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)
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'); }
▍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?
