22 Tipps für einen Angular-Entwickler. Teil 1

Der Autor des Artikels, dessen erster Teil der Übersetzung wir veröffentlichen, sagt, dass er seit etwa zwei Jahren an einer groß angelegten Angular-Anwendung in Trade Me arbeitet . In den letzten Jahren hat das Anwendungsentwicklungsteam das Projekt sowohl hinsichtlich der Codequalität als auch der Leistung ständig verbessert.


Diese Reihe von Materialien konzentriert sich auf die Entwicklungsansätze des Trade Me-Teams, die in Form von mehr als zwei Dutzend Empfehlungen zu Technologien wie Angular, TypeScript, RxJS und @ ngrx / store zum Ausdruck kommen. Darüber hinaus wird universellen Programmiertechniken besondere Aufmerksamkeit gewidmet, die darauf abzielen, den Anwendungscode sauberer und genauer zu machen.

1. Über trackBy


Verwenden ngFor dieses Konstrukt mit ngFor zum Durchlaufen von Arrays in Vorlagen mit der Funktion trackBy , die für jedes Element einen eindeutigen Bezeichner zurückgibt.

▍ Erklärungen


Wenn sich das Array ändert, rendert Angular den gesamten DOM-Baum neu. Wenn Sie jedoch trackBy , weiß das System, welches Element geändert wurde, und nimmt Änderungen am DOM vor, die nur für dieses bestimmte Element gelten. Details dazu finden Sie hier .

»Um


 <li *ngFor="let item of items;">{{ item }}</li> 

▍Nachher


 //   <li *ngFor="let item of items; trackBy: trackByFn">{{ item }}</li> //   trackByFn(index, item) {     return item.id; //  id,   } 

2. Schlüsselwörter const und let


Wenn Sie eine Variable deklarieren möchten, deren Wert Sie nicht ändern const , verwenden Sie das Schlüsselwort const .

▍ Erklärungen


Die angemessene Verwendung der Schlüsselwörter let und const verdeutlicht die Absicht hinsichtlich der Verwendung von Entitäten, deren Verwendung deklariert wurde. Darüber hinaus erleichtert dieser Ansatz das Erkennen von Problemen, die durch versehentliches Überschreiben konstanter Werte verursacht werden. In dieser Situation wird ein Kompilierungsfehler ausgelöst. Darüber hinaus wird die Lesbarkeit des Codes verbessert.

»Um


 let car = 'ludicrous car'; let myCar = `My ${car}`; let yourCar = `Your ${car}; if (iHaveMoreThanOneCar) {  myCar = `${myCar}s`; } if (youHaveMoreThanOneCar) {  yourCar = `${youCar}s`; } 

▍Nachher


 //  car  ,     car  const car = 'ludicrous car'; let myCar = `My ${car}`; let yourCar = `Your ${car}; if (iHaveMoreThanOneCar) {  myCar = `${myCar}s`; } if (youHaveMoreThanOneCar) {  yourCar = `${youCar}s`; } 

3. Fördererbetreiber


Verwenden Sie bei der Arbeit mit RxJS Pipeline-Operatoren.

▍ Erklärungen


Die übermittelten Operatoren unterstützen den Tree-Shake-Algorithmus, dh, wenn sie importiert werden, wird nur der Code in das Projekt aufgenommen, dessen Ausführung geplant ist. Dies macht es auch einfach, nicht verwendete Anweisungen in Dateien zu identifizieren.

Bitte beachten Sie, dass diese Empfehlung für Angular Version 5.5 und höher relevant ist.

»Um


 import 'rxjs/add/operator/map'; import 'rxjs/add/operator/take'; iAmAnObservable   .map(value => value.item)   .take(1); 

▍Nachher


 import { map, take } from 'rxjs/operators'; iAmAnObservable   .pipe(      map(value => value.item),      take(1)    ); 

4. Isolierung von API-Fixes


Nicht alle APIs sind vollständig stabil und fehlerfrei. Daher ist es manchmal erforderlich, eine Logik in den Code einzuführen, um API-Probleme zu beheben. Anstatt diese Logik in Komponenten zu platzieren, in denen gepatchte APIs verwendet werden, ist es besser, sie irgendwo zu isolieren, z. B. in einem Dienst, und auf den entsprechenden Dienst zu verweisen, anstatt auf die problematische API der Komponente.

▍ Erklärungen


Der vorgeschlagene Ansatz ermöglicht es, Korrekturen „näher“ an der API zu halten, dh so nah wie möglich an dem Code, aus dem Netzwerkanforderungen gestellt werden. Infolgedessen wird die Menge an Anwendungscode, der mit problematischen APIs interagiert, reduziert. Darüber hinaus stellt sich heraus, dass sich alle Korrekturen an einem Ort befinden, wodurch es einfacher wird, mit ihnen zu arbeiten. Wenn Sie Fehler in der API beheben müssen, ist es viel einfacher, dies in einer einzigen Datei zu tun, als diese Korrekturen in der gesamten Anwendung zu verteilen. Dies erleichtert nicht nur die Erstellung von Korrekturen, sondern auch die Suche nach dem geeigneten Code im Projekt und dessen Unterstützung.

Darüber hinaus können Sie eigene Tags erstellen, z. B. API_FIX (ähnlich dem TODO Tag), und Korrekturen damit API_FIX . Dies erleichtert das Auffinden solcher Korrekturen.

5. Abonnement in der Vorlage


Vermeiden Sie das Abonnieren von Observablen aus Komponenten. Abonnieren Sie sie stattdessen in Vorlagen.

▍ Erklärungen


Asynchrone Pipeline-Betreiber melden sich automatisch ab, was den Code vereinfacht und die manuelle Abonnementverwaltung überflüssig macht. Dies verringert außerdem das Risiko, dass der Entwickler vergisst, sich von der Komponente abzumelden, was zu Speicherlecks führen kann. Es ist möglich, die Wahrscheinlichkeit von Speicherlecks mithilfe von Linter-Regeln zu verringern, die darauf abzielen, beobachtbare Objekte zu identifizieren, von denen sie sich nicht abgemeldet haben.

Darüber hinaus führt die Anwendung dieses Ansatzes dazu, dass die Komponenten keine statusbehafteten Komponenten mehr sind, was zu Fehlern führen kann, wenn sich Daten außerhalb des Abonnements ändern.

»Um


 //  <p>{{ textToDisplay }}</p> //  iAmAnObservable   .pipe(      map(value => value.item),      takeUntil(this._destroyed$)    )   .subscribe(item => this.textToDisplay = item); 

▍Nachher


 //  <p>{{ textToDisplay$ | async }}</p> //  this.textToDisplay$ = iAmAnObservable   .pipe(      map(value => value.item)    ); 

6. Abonnements entfernen


takeUntil beim Abonnieren von überwachten Objekten immer sicher, dass Abonnements mit Operatoren wie take , takeUntil usw. korrekt gelöscht werden.

▍ Erklärungen


Wenn Sie das beobachtete Objekt nicht abbestellen, führt dies zu Speicherverlusten, da der Stream des beobachteten Objekts offen bleibt. Dies ist auch möglich, nachdem die Komponente zerstört wurde oder nachdem der Benutzer eine andere Seite der Anwendung aufgerufen hat.

Noch besser wäre es, eine Linter-Regel zu erstellen, um beobachtete Objekte mit einem gültigen Abonnement zu erkennen.

»Um


 iAmAnObservable   .pipe(      map(value => value.item)        )   .subscribe(item => this.textToDisplay = item); 

▍Nachher


Verwenden Sie den Operator takeUntil , wenn Sie die Änderungen eines Objekts beobachten möchten, bis ein anderes beobachtetes Objekt einen bestimmten Wert generiert:

 private destroyed$ = new Subject(); public ngOnInit (): void {   iAmAnObservable   .pipe(      map(value => value.item)     //    iAmAnObservable         takeUntil(this._destroyed$)    )   .subscribe(item => this.textToDisplay = item); } public ngOnDestroy (): void {   this._destroyed$.next(); } 

Die Verwendung von so etwas ist ein Muster, mit dem das Entfernen von Abonnements für viele beobachtete Objekte in einer Komponente gesteuert wird.

Verwenden Sie take wenn Sie nur den ersten Wert benötigen, der vom beobachteten Objekt zurückgegeben wird:

 iAmAnObservable   .pipe(      map(value => value.item),      take(1),      takeUntil(this._destroyed$)   )   .subscribe(item => this.textToDisplay = item); 

Bitte beachten Sie, dass wir hier takeUntil mit take . Dies geschieht, um Speicherverluste zu vermeiden, die dadurch verursacht werden, dass das Abonnement erst nach Zerstörung der Komponente zum Abrufen des Werts führte. Wenn die Funktion takeUntil hier nicht verwendet würde, würde das Abonnement bestehen, bis der erste Wert empfangen wurde. Da die Komponente jedoch bereits zerstört worden wäre, wäre dieser Wert niemals empfangen worden, was zu einem Speicherverlust führen würde.

7. Verwenden geeigneter Operatoren


Wenden Sie mithilfe von Glättungsoperatoren mit beobachtbaren Objekten diejenigen an, die den Merkmalen des zu lösenden Problems entsprechen.

  • Verwenden Sie switchMap wenn Sie die vorherige geplante Aktion ignorieren müssen, wenn eine neue Aktion eintrifft.
  • Verwenden Sie mergeMap falls Sie alle mergeMap Aktionen parallel verarbeiten müssen.
  • Verwenden Sie concatMap wenn Aktionen nacheinander in der Reihenfolge concatMap verarbeitet werden müssen.
  • Verwenden Sie exhaustMap in Situationen, in denen Sie bei der Verarbeitung zuvor empfangener Aktionen neue ignorieren müssen.

Details dazu finden Sie hier .

▍ Erklärungen


Wenn Sie nach Möglichkeit einen Operator verwenden, anstatt den gleichen Effekt durch Kombinieren mehrerer Operatoren in einer Kette zu erzielen, wird die Menge an Anwendungscode reduziert, die an den Benutzer gesendet werden muss. Die Verwendung eines falsch ausgewählten Operators kann zu einem falschen Programmverhalten führen, da verschiedene Operatoren beobachtete Objekte unterschiedlich verarbeiten.

8. Faules Laden


Versuchen Sie dann, wann immer möglich, das verzögerte Laden von Modulen der Angular-Anwendung zu organisieren. Diese Technik beruht auf der Tatsache, dass etwas nur geladen wird, wenn es verwendet wird. Beispielsweise wird eine Komponente nur geladen, wenn sie angezeigt werden muss.

▍ Erklärungen


Durch verzögertes Laden wird die Größe der Anwendungsmaterialien reduziert, die der Benutzer herunterladen muss. Dies kann die Download-Geschwindigkeit der Anwendung verbessern, da nicht verwendete Module nicht vom Server auf Clients übertragen werden.

»Um


 // app.routing.ts { path: 'not-lazy-loaded', component: NotLazyLoadedComponent } 

▍Nachher


 // app.routing.ts { path: 'lazy-load', loadChildren: 'lazy-load.module#LazyLoadModule' } // lazy-load.module.ts import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { LazyLoadComponent }   from './lazy-load.component'; @NgModule({ imports: [   CommonModule,   RouterModule.forChild([        {            path: '',            component: LazyLoadComponent        }   ]) ], declarations: [   LazyLoadComponent ] }) export class LazyModule {} 

9. Informationen zu Abonnements in anderen Abonnements


Manchmal benötigen Sie zum Ausführen einer Aktion Daten von mehreren beobachtbaren Objekten. Vermeiden Sie in einer solchen Situation, Abonnements für solche Objekte in den subscribe anderer beobachtbarer Objekte zu erstellen. Verwenden Sie stattdessen geeignete Operatoren, um Befehle zu verketten. Unter solchen Operatoren kann man withLatestFrom und combineLatest . Betrachten Sie die Beispiele und kommentieren Sie sie dann.

»Um


 firstObservable$.pipe(  take(1) ) .subscribe(firstValue => {   secondObservable$.pipe(       take(1)   )   .subscribe(secondValue => {       console.log(`Combined values are: ${firstValue} & ${secondValue}`);   }); }); 

▍Nachher


 firstObservable$.pipe(   withLatestFrom(secondObservable$),   first() ) .subscribe(([firstValue, secondValue]) => {   console.log(`Combined values are: ${firstValue} & ${secondValue}`); }); 

▍ Erklärungen


Wenn wir über die Lesbarkeit, die Komplexität des Codes oder die Anzeichen von fehlerhaftem Code sprechen, bedeutet dies, dass der Entwickler die RxJS-API nicht gut kennt, wenn das Programm die RxJS-Funktionen nicht vollständig nutzt. Wenn wir das Thema Leistung firstObservable , stellt sich heraus, dass wenn das beobachtete Objekt einige Zeit zum Initialisieren benötigt, es firstObservable abonniert, das System auf den Abschluss des Vorgangs wartet und erst danach die Arbeit mit dem zweiten beobachteten Objekt beginnt. Wenn es sich bei diesen Objekten um Netzwerkanforderungen handelt, sieht es wie eine synchrone Ausführung von Anforderungen aus.

10. Über das Tippen


Versuchen Sie immer, Variablen oder Konstanten mit einem anderen Typ als einem anderen zu deklarieren.

▍ Erklärungen


Wenn eine Variable oder Konstante in TypeScript ohne Angabe eines Typs deklariert wird, wird der Typ basierend auf dem ihm zugewiesenen Wert abgeleitet. Dies kann zu Problemen führen. Betrachten Sie ein klassisches Beispiel für das Systemverhalten in einer ähnlichen Situation:

 const x = 1; const y = 'a'; const z = x + y; console.log(`Value of z is: ${z}` //  Value of z is 1a 

Es wird angenommen, dass y hier eine Zahl ist, aber unser Programm weiß nichts darüber, sodass etwas angezeigt wird, das falsch aussieht, aber keine Fehlermeldungen erzeugt. Ähnliche Probleme können vermieden werden, indem Variablen und Konstanten geeignete Typen zugewiesen werden.

Wir schreiben das obige Beispiel neu:

 const x: number = 1; const y: number = 'a'; const z: number = x + y; //    : Type '"a"' is not assignable to type 'number'. const y:number 

Dies hilft, Datentypfehler zu vermeiden.

Ein weiterer Vorteil eines systematischen Typisierungsansatzes besteht darin, dass er das Refactoring vereinfacht und die Wahrscheinlichkeit von Fehlern während dieses Prozesses verringert.

Betrachten Sie ein Beispiel:

 public ngOnInit (): void {   let myFlashObject = {       name: 'My cool name',       age: 'My cool age',       loc: 'My cool location'   }   this.processObject(myFlashObject); } public processObject(myObject: any): void {   console.log(`Name: ${myObject.name}`);   console.log(`Age: ${myObject.age}`);   console.log(`Location: ${myObject.loc}`); } //  Name: My cool name Age: My cool age Location: My cool location 

Angenommen, wir wollten im myFlashObject den Namen der loc Eigenschaft in location ändern und haben myFlashObject Bearbeiten des Codes einen Fehler gemacht:

 public ngOnInit (): void {   let myFlashObject = {       name: 'My cool name',       age: 'My cool age',       location: 'My cool location'   }   this.processObject(myFlashObject); } public processObject(myObject: any): void {   console.log(`Name: ${myObject.name}`);   console.log(`Age: ${myObject.age}`);   console.log(`Location: ${myObject.loc}`); } //  Name: My cool name Age: My cool age Location: undefined 

Wenn beim Erstellen des myFlashObject Objekts keine Eingabe verwendet wird, geht das System in unserem Fall davon aus, dass der Wert der Eigenschaft loc von myFlashObject undefined . Sie glaubt nicht, dass loc ein ungültiger Eigenschaftsname sein könnte.

Wenn bei der Beschreibung des myFlashObject Objekts die myFlashObject wird, wird in einer ähnlichen Situation beim Kompilieren des Codes eine wunderbare Fehlermeldung angezeigt:

 type FlashObject = {   name: string,   age: string,   location: string } public ngOnInit (): void {   let myFlashObject: FlashObject = {       name: 'My cool name',       age: 'My cool age',       //         Type '{ name: string; age: string; loc: string; }' is not assignable to type 'FlashObjectType'.       Object literal may only specify known properties, and 'loc' does not exist in type 'FlashObjectType'.       loc: 'My cool location'   }   this.processObject(myFlashObject); } public processObject(myObject: FlashObject): void {   console.log(`Name: ${myObject.name}`);   console.log(`Age: ${myObject.age}`)   //     Property 'loc' does not exist on type 'FlashObjectType'.   console.log(`Location: ${myObject.loc}`); } 

Wenn Sie mit der Arbeit an einem neuen Projekt beginnen, ist es hilfreich, in der Datei tsconfig.json die Option strict:true tsconfig.json , um die strikte Typprüfung zu aktivieren.

11. Über die Verwendung von Linter


Tslint hat verschiedene Standardregeln wie No- Any , No-Magic- Numbers , No-Console . Der Linter kann durch Bearbeiten der Datei tslint.json angepasst werden, um die tslint.json nach bestimmten Regeln zu organisieren.

▍ Erklärungen


Die Verwendung eines Linter zur Überprüfung des Codes bedeutet, dass Sie eine Fehlermeldung erhalten, wenn in dem Code etwas erscheint, das durch die Regeln verboten ist. Dies trägt zur Einheitlichkeit des Projektcodes bei und verbessert dessen Lesbarkeit. Weitere tslint-Regeln finden Sie hier.

Es ist zu beachten, dass einige Regeln Mittel zur Korrektur dessen enthalten, was als unzulässig angesehen wird. Bei Bedarf können Sie Ihre eigenen Regeln erstellen. Wenn Sie interessiert sind, schauen Sie sich dieses Material an, in dem die Erstellung benutzerdefinierter Regeln für den Linter mithilfe von TSQuery erläutert wird .

»Um


 public ngOnInit (): void {   console.log('I am a naughty console log message');   console.warn('I am a naughty console warning message');   console.error('I am a naughty console error message'); } // .    ,    : I am a naughty console message I am a naughty console warning message I am a naughty console error message 

▍Nachher


 // tslint.json {   "rules": {       .......       "no-console": [            true,            "log", //  console.log             "warn" //  console.warn        ]  } } // ..component.ts public ngOnInit (): void {   console.log('I am a naughty console log message');   console.warn('I am a naughty console warning message');   console.error('I am a naughty console error message'); } // .      console.log and console.warn        console.error,         Calls to 'console.log' are not allowed. Calls to 'console.warn' are not allowed. 

Zusammenfassung


Heute haben wir 11 Empfehlungen überprüft, von denen wir hoffen, dass sie für Angular-Entwickler nützlich sind. Warten Sie das nächste Mal auf 11 weitere Tipps.

Liebe Leser! Verwenden Sie das Angular-Framework für die Entwicklung von Webprojekten?

Source: https://habr.com/ru/post/de425661/


All Articles