22 conseils pour un développeur Angular. Partie 1

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)     //    iAmAnObservable         takeUntil(this._destroyed$)    )   .subscribe(item => this.textToDisplay = item); } public ngOnDestroy (): void {   this._destroyed$.next(); } 

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'); } // .    ,    : I am a naughty console message I am a naughty console warning message 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?

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


All Articles