L'auteur de l'article, dont nous publions la première partie de la traduction, dit qu'il travaille sur une application angulaire à grande échelle dans
Trade Me depuis environ deux ans maintenant. Au cours des dernières années, l'équipe de développement d'applications a constamment amélioré le projet, tant en termes de qualité de code que de performances.
Cette série de documents se concentrera sur les approches de développement utilisées par l'équipe Trade Me, qui sont exprimées sous la forme de plus de deux douzaines de recommandations concernant des technologies telles que Angular, TypeScript, RxJS et @ ngrx / store. En outre, une certaine attention sera accordée aux techniques de programmation universelles qui visent à rendre le code d'application plus propre et plus précis.
1. À propos de trackBy
À l'aide de
ngFor
pour parcourir les tableaux dans les modèles, utilisez cette construction avec la fonction
trackBy
, qui renvoie un identifiant unique pour chaque élément.
▍ Explications
Lorsque le tableau change, Angular restitue la totalité de l'arborescence DOM. Mais si vous utilisez
trackBy
, le système saura quel élément a changé et apportera des modifications au DOM qui s'appliquent uniquement à cet élément particulier. Des détails à ce sujet peuvent être trouvés
ici .
OPour
<li *ngFor="let item of items;">{{ item }}</li>
▍Après
// <li *ngFor="let item of items; trackBy: trackByFn">{{ item }}</li> // trackByFn(index, item) { return item.id; // id, }
2. Mots-clés const et let
Si vous allez déclarer une variable dont vous ne prévoyez pas de changer la valeur, utilisez le mot clé
const
.
▍ Explications
L'utilisation appropriée des mots clés
let
et
const
clarifie l'intention concernant l'utilisation des entités déclarées les utilisant. De plus, cette approche facilite la reconnaissance des problèmes causés par l'écrasement accidentel de valeurs constantes. Dans cette situation, une erreur de compilation est levée. De plus, il améliore la lisibilité du code.
OPour
let car = 'ludicrous car'; let myCar = `My ${car}`; let yourCar = `Your ${car}; if (iHaveMoreThanOneCar) { myCar = `${myCar}s`; } if (youHaveMoreThanOneCar) { yourCar = `${youCar}s`; }
▍Après
// 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. Opérateurs de convoyeurs
Lorsque vous travaillez avec RxJS, utilisez des opérateurs pipelinés.
▍ Explications
Les opérateurs véhiculés prennent en charge l'algorithme de tremblement d'arbre, c'est-à-dire que lorsqu'ils sont importés, seul le code dont l'exécution est planifiée sera inclus dans le projet. Cela facilite également l'identification des instructions inutilisées dans les fichiers.
Veuillez noter que cette recommandation s'applique à la version Angular 5.5 et supérieure.
OPour
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/take'; iAmAnObservable .map(value => value.item) .take(1);
▍Après
import { map, take } from 'rxjs/operators'; iAmAnObservable .pipe( map(value => value.item), take(1) );
4. Isolement des correctifs API
Toutes les API ne sont pas complètement stables et sans erreur. Par conséquent, il est parfois nécessaire d'introduire une logique dans le code visant à résoudre les problèmes d'API. Au lieu de placer cette logique dans des composants où des API corrigées sont utilisées, il serait préférable de l'isoler quelque part, par exemple dans un service, et de faire référence au service correspondant plutôt qu'à l'API problématique du composant.
▍ Explications
L'approche proposée permet de garder les corrections «plus proches» de l'API, c'est-à-dire aussi près que possible du code à partir duquel les requêtes réseau sont effectuées. Par conséquent, la quantité de code d'application interagissant avec les API problématiques est réduite. De plus, il s'avère que toutes les corrections se trouvent au même endroit, il sera donc plus facile de travailler avec elles. Si vous devez corriger des erreurs dans l'API, il est beaucoup plus facile de le faire dans un seul fichier que de répartir ces corrections dans l'application. Cela facilite non seulement la création de corrections, mais aussi la recherche du code approprié dans le projet, et son support.
De plus, vous pouvez créer vos propres balises, telles que
API_FIX
(qui ressemble à la balise
TODO
), et
API_FIX
corrections avec elles. Cela facilite la recherche de ces correctifs.
5. Abonnement dans le modèle
Évitez de vous abonner aux observables des composants. Au lieu de cela, abonnez-vous à eux dans des modèles.
▍ Explications
Les opérateurs pipelinés asynchrones se désabonnent automatiquement, ce qui simplifie le code, éliminant ainsi la nécessité d'une gestion manuelle des abonnements. De plus, cela réduit le risque que le développeur oublie de se désinscrire du composant, ce qui peut entraîner des fuites de mémoire. Il est possible de réduire la probabilité de fuites de mémoire en utilisant des règles de linter visant à identifier les objets observables dont ils ne se sont pas désabonnés.
De plus, l'application de cette approche conduit au fait que les composants cessent d'être des composants avec état, ce qui peut entraîner des erreurs lorsque les données changent en dehors de l'abonnement.
OPour
// <p>{{ textToDisplay }}</p> // iAmAnObservable .pipe( map(value => value.item), takeUntil(this._destroyed$) ) .subscribe(item => this.textToDisplay = item);
▍Après
// <p>{{ textToDisplay$ | async }}</p> // this.textToDisplay$ = iAmAnObservable .pipe( map(value => value.item) );
6. Suppression d'abonnements
Lorsque vous vous abonnez à des objets surveillés, assurez-vous toujours que les abonnements à ceux-ci sont supprimés correctement à l'aide d'opérateurs tels que
take
,
takeUntil
etc.
▍ Explications
Si vous ne vous désabonnez pas de l'objet observé, cela entraînera des fuites de mémoire, car le flux de l'objet observé restera ouvert, ce qui est possible même après la destruction du composant ou après que l'utilisateur accède à une autre page de l'application.
Encore mieux serait de créer une règle de linter pour détecter les objets observés avec un abonnement valide à eux.
OPour
iAmAnObservable .pipe( map(value => value.item) ) .subscribe(item => this.textToDisplay = item);
▍Après
Utilisez l'opérateur
takeUntil
si vous souhaitez observer les changements d'un objet jusqu'à ce qu'un autre objet observé génère une certaine valeur:
private destroyed$ = new Subject(); public ngOnInit (): void { iAmAnObservable .pipe( map(value => value.item)
L'utilisation de quelque chose comme
this
est un modèle utilisé pour contrôler la suppression des abonnements à de nombreux objets observés dans un composant.
Utilisez
take
si vous n'avez besoin que de la première valeur renvoyée par l'objet observé:
iAmAnObservable .pipe( map(value => value.item), take(1), takeUntil(this._destroyed$) ) .subscribe(item => this.textToDisplay = item);
Veuillez noter que nous utilisons ici
takeUntil
avec
take
. Cette opération est effectuée afin d'éviter les fuites de mémoire provoquées par le fait que l'abonnement n'a pas permis d'obtenir la valeur tant que le composant n'a pas été détruit. Si la fonction
takeUntil
n'était pas utilisée
takeUntil
, l'abonnement existerait jusqu'à la réception de la première valeur, mais puisque le composant aurait déjà été détruit, cette valeur n'aurait jamais été reçue, ce qui entraînerait une fuite de mémoire.
7. Utilisation d'opérateurs appropriés
En utilisant des opérateurs de lissage avec des objets observables, appliquez ceux qui correspondent aux caractéristiques du problème à résoudre.
- Utilisez
switchMap
lorsque vous devez ignorer l'action planifiée précédente lorsqu'une nouvelle action arrive. - Utilisez
mergeMap
au cas où vous auriez besoin de traiter toutes les actions distribuées en parallèle. - Utilisez
concatMap
lorsque les actions doivent être traitées l'une après l'autre, dans l'ordre où elles sont reçues. - Utilisez
exhaustMap
dans les situations où, lors du traitement des actions reçues précédemment, vous devez ignorer les nouvelles.
Des détails à ce sujet peuvent être trouvés
ici .
▍ Explications
L'utilisation, si possible, d'un opérateur, au lieu d'obtenir le même effet en combinant plusieurs opérateurs dans une chaîne, réduit la quantité de code d'application qui doit être envoyée à l'utilisateur. L'utilisation d'un opérateur mal sélectionné peut entraîner un comportement incorrect du programme, car différents opérateurs traitent les objets observés différemment.
8. Chargement paresseux
Ensuite, dans la mesure du possible, essayez d'organiser le chargement paresseux des modules de l'application angulaire. Cette technique se résume au fait que quelque chose ne se charge que s'il est utilisé. Par exemple, un composant n'est chargé que lorsqu'il doit être affiché.
▍ Explications
Le chargement différé réduit la taille du matériel d'application que l'utilisateur doit télécharger. Cela peut améliorer la vitesse de téléchargement de l'application car les modules inutilisés ne sont pas transférés du serveur vers les clients.
OPour
// app.routing.ts { path: 'not-lazy-loaded', component: NotLazyLoadedComponent }
▍Après
// 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. À propos des abonnements dans d'autres abonnements
Parfois, pour effectuer une action, vous pouvez avoir besoin de données provenant de plusieurs objets observables. Dans une telle situation, évitez de créer des abonnements à ces objets à l'intérieur des blocs d'
subscribe
d'autres objets observables. Utilisez plutôt des opérateurs appropriés pour chaîner les commandes. Parmi ces opérateurs, on peut noter
withLatestFrom
et
combineLatest
. Examinez les exemples, puis commentez-les.
OPour
firstObservable$.pipe( take(1) ) .subscribe(firstValue => { secondObservable$.pipe( take(1) ) .subscribe(secondValue => { console.log(`Combined values are: ${firstValue} & ${secondValue}`); }); });
▍Après
firstObservable$.pipe( withLatestFrom(secondObservable$), first() ) .subscribe(([firstValue, secondValue]) => { console.log(`Combined values are: ${firstValue} & ${secondValue}`); });
▍ Explications
Si nous parlons de lisibilité, de la complexité du code ou des signes de mauvais code, alors lorsque le programme n'utilise pas pleinement les fonctionnalités RxJS, cela signifie que le développeur ne connaît pas bien l'API RxJS. Si nous abordons le sujet des performances, il s'avère que si l'objet observé a besoin de temps pour s'initialiser, il s'abonnera à
firstObservable
, puis le système attendra que l'opération se termine, et ce n'est qu'après que le travail avec le deuxième objet observé commencera. Si ces objets sont des requêtes réseau, cela ressemblera à une exécution synchrone des requêtes.
10. À propos de la frappe
Essayez toujours de déclarer des variables ou des constantes avec un type autre que
any
.
▍ Explications
Si une variable ou une constante est déclarée dans TypeScript sans spécifier de type, le type sera déduit en fonction de la valeur qui lui est affectée. Cela peut entraîner des problèmes. Prenons un exemple classique de comportement du système dans une situation similaire:
const x = 1; const y = 'a'; const z = x + y; console.log(`Value of z is: ${z}` // Value of z is 1a
Il est supposé que
y
est un nombre ici, mais notre programme ne le sait pas, il affiche donc quelque chose qui ne va pas, mais ne produit aucun message d'erreur. Des problèmes similaires peuvent être évités en affectant des types appropriés aux variables et aux constantes.
Nous réécrivons l'exemple ci-dessus:
const x: number = 1; const y: number = 'a'; const z: number = x + y; // : Type '"a"' is not assignable to type 'number'. const y:number
Cela permet d'éviter les erreurs de type de données.
Un autre avantage d'une approche systématique du typage est qu'elle simplifie le refactoring et réduit la probabilité d'erreurs au cours de ce processus.
Prenons un exemple:
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
Supposons que nous voulions changer, dans
myFlashObject
, le nom de la propriété
loc
en
location
et que nous avons fait une erreur
myFlashObject
modification du code:
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
Si la saisie n'est pas utilisée lors de la création de l'objet
myFlashObject
, dans notre cas, le système suppose que la valeur de la propriété
loc
de
myFlashObject
n'est
undefined
. Elle ne pense pas que
loc
puisse être un nom de propriété invalide.
Si la saisie est utilisée lors de la description de l'objet
myFlashObject
, alors dans une situation similaire, nous verrons, lors de la compilation du code, un merveilleux message d'erreur:
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}`); }
Si vous commencez à travailler sur un nouveau projet, il sera utile de définir, dans le fichier
tsconfig.json
, l'option
strict:true
afin d'activer la vérification de type stricte.
11. À propos de l'utilisation de linter
Tslint a diverses règles standard comme
no-any ,
no-magic-numbers ,
no-console . Le linter peut être personnalisé en éditant le fichier
tslint.json
afin d'organiser la vérification du code selon certaines règles.
▍ Explications
L'utilisation d'un linter pour vérifier le code signifie que si quelque chose apparaît dans le code qui est interdit par les règles, vous recevrez un message d'erreur. Cela contribue à l'uniformité du code du projet, améliore sa lisibilité. Voir les autres règles tslint ici.
Il convient de noter que certaines règles prévoient des moyens de corriger ce qui est considéré comme irrecevable conformément à celles-ci. Si nécessaire, vous pouvez créer vos propres règles. Si vous êtes intéressé, jetez un œil à
ce document, qui traite de la création de règles personnalisées pour le linter à l'aide de
TSQuery .
OPour
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'); }
▍Après
// 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.
Résumé
Aujourd'hui, nous avons examiné 11 recommandations qui, nous l'espérons, seront utiles aux développeurs Angular. La prochaine fois, attendez 11 autres conseils.
Chers lecteurs! Utilisez-vous le framework Angular pour développer des projets web?
