22 consejos para un desarrollador angular. Parte 1

El autor del artículo, cuya primera parte de la traducción estamos publicando, dice que ha estado trabajando en una aplicación angular a gran escala en Trade Me durante aproximadamente dos años. En los últimos años, el equipo de desarrollo de aplicaciones ha estado mejorando constantemente el proyecto, tanto en términos de calidad de código como de rendimiento.


Esta serie de materiales se centrará en los enfoques de desarrollo utilizados por el equipo de Trade Me, que se expresan en forma de más de dos docenas de recomendaciones sobre tecnologías como Angular, TypeScript, RxJS y @ ngrx / store. Además, se prestará atención a las técnicas de programación universal destinadas a hacer que el código de la aplicación sea más limpio y preciso.

1. Acerca de trackBy


Usando ngFor para recorrer matrices en plantillas, use esta construcción con la función trackBy , que devuelve un identificador único para cada elemento.

▍ Explicaciones


Cuando la matriz cambia, Angular vuelve a representar todo el árbol DOM. Pero si usa trackBy , el sistema sabrá qué elemento ha cambiado y realizará cambios en el DOM que se aplican solo a ese elemento en particular. Los detalles sobre esto se pueden encontrar aquí .

▍To


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

▍Después


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

2. Palabras clave const y let


Si va a declarar una variable cuyo valor no planea cambiar, use la palabra clave const .

▍ Explicaciones


El uso apropiado de las palabras clave let y const aclara la intención con respecto al uso de entidades declaradas que las usan. Además, este enfoque facilita el reconocimiento de los problemas causados ​​por sobrescribir accidentalmente valores constantes. En esta situación, se produce un error de compilación. Además, mejora la legibilidad del código.

▍To


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

▍Despué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. Operadores de transportadores


Cuando trabaje con RxJS, use operadores canalizados.

▍ Explicaciones


Los operadores transportados admiten el algoritmo de vibración de árbol, es decir, cuando se importan, solo se incluirá en el proyecto el código que se planea ejecutar. Esto también facilita la identificación de declaraciones no utilizadas en los archivos.

Tenga en cuenta que esta recomendación es relevante para Angular versión 5.5 y superior.

▍To


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

▍Después


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

4. Aislamiento de correcciones de API


No todas las API son completamente estables y sin errores. Por lo tanto, a veces es necesario introducir algo de lógica en el código destinado a solucionar problemas de API. En lugar de colocar esta lógica en componentes donde se usan API parcheadas, sería mejor aislarla en algún lugar, por ejemplo, en un servicio, y referirse desde el componente ya no es a la API problemática, sino al servicio correspondiente.

▍ Explicaciones


El enfoque propuesto permite mantener las correcciones "más cerca" de la API, es decir, lo más cerca posible del código desde el que se realizan las solicitudes de red. Como resultado, se reduce la cantidad de código de aplicación que interactúa con API problemáticas. Además, resulta que todas las correcciones están en un solo lugar, como resultado será más fácil trabajar con ellas. Si tiene que corregir errores en la API, entonces es mucho más fácil hacer esto en un solo archivo que distribuir estas correcciones en toda la aplicación. Esto facilita no solo la creación de correcciones, sino también la búsqueda del código apropiado en el proyecto y su soporte.

Además, puede crear sus propias etiquetas, como API_FIX (que se parece a la etiqueta TODO ), y marcar las correcciones con ellas. Esto facilita encontrar tales soluciones.

5. Suscripción en la plantilla.


Evite suscribirse a observables desde componentes. En cambio, suscríbase a ellos en plantillas.

▍ Explicaciones


Los operadores canalizados asíncronos se anulan automáticamente, lo que simplifica el código y elimina la necesidad de una administración de suscripción manual. Esto, además, reduce el riesgo de que el desarrollador se olvide de darse de baja del componente, lo que puede provocar pérdidas de memoria. Es posible reducir la probabilidad de pérdidas de memoria utilizando reglas de interinterruptor destinadas a identificar objetos observables de los que no se dieron de baja.

Además, la aplicación de este enfoque lleva al hecho de que los componentes dejan de ser componentes con estado, lo que puede generar errores cuando los datos cambian fuera de la suscripción.

▍To


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

▍Después


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

6. Eliminar suscripciones


Al suscribirse a objetos supervisados, asegúrese siempre de que las suscripciones a ellos se eliminen correctamente utilizando operadores como take , takeUntil etc.

▍ Explicaciones


Si no se da de baja del objeto observado, esto provocará pérdidas de memoria, ya que la secuencia del objeto observado permanecerá abierta, lo que es posible incluso después de que se destruya el componente o después de que el usuario vaya a otra página de la aplicación.

Aún mejor sería crear una regla linter para detectar objetos observados con una suscripción válida para ellos.

▍To


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

▍Después


Use el operador takeUntil si desea observar los cambios de un objeto hasta que otro objeto observado genere un cierto valor:

 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(); } 

Usar algo como this es un patrón utilizado para controlar la eliminación de suscripciones a muchos objetos observados en un componente.

Use take si solo necesita el primer valor devuelto por el objeto observado:

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

Tenga en cuenta que aquí usamos takeUntil con take . Esto se hace para evitar pérdidas de memoria causadas por el hecho de que la suscripción no condujo a obtener el valor hasta que se destruyó el componente. Si la función takeUntil no se usara takeUntil , la suscripción existiría hasta que se recibiera el primer valor, pero como el componente ya se habría destruido, este valor nunca se habría recibido, lo que provocaría una pérdida de memoria.

7. Utilizando operadores adecuados


Usando operadores de suavizado con objetos observables, aplique aquellos que correspondan a las características del problema que se está resolviendo.

  • Use switchMap cuando necesite ignorar la acción programada anterior cuando llegue una nueva acción.
  • Use mergeMap en caso de que necesite procesar todas las acciones despachadas en paralelo.
  • Utilice concatMap cuando las acciones deban procesarse una tras otra, en el orden en que se reciben.
  • Use exhaustMap en situaciones en las que, en el proceso de procesamiento de acciones recibidas anteriormente, necesite ignorar otras nuevas.

Los detalles sobre esto se pueden encontrar aquí .

▍ Explicaciones


Usar, si es posible, un operador, en lugar de lograr el mismo efecto combinando varios operadores en una cadena, reduce la cantidad de código de aplicación que debe enviarse al usuario. El uso de un operador seleccionado incorrectamente puede conducir a un comportamiento incorrecto del programa, ya que diferentes operadores procesan los objetos observados de manera diferente.

8. Carga perezosa


Luego, siempre que sea posible, intente organizar la carga diferida de los módulos de la aplicación angular. Esta técnica se reduce al hecho de que algo se carga solo si se usa. Por ejemplo, un componente se carga solo cuando debe mostrarse.

▍ Explicaciones


La carga diferida reduce el tamaño de los materiales de la aplicación que el usuario tiene que descargar. Esto puede mejorar la velocidad de descarga de la aplicación debido al hecho de que los módulos no utilizados no se transfieren del servidor a los clientes.

▍To


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

▍Despué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. Acerca de las suscripciones dentro de otras suscripciones


A veces, para realizar alguna acción, es posible que necesite datos de varios objetos observables. En tal situación, evite crear suscripciones a dichos objetos dentro de los bloques de subscribe de otros objetos observables. En su lugar, utilice operadores adecuados para encadenar comandos. Entre tales operadores, uno puede notar con withLatestFrom y combineLatest . Considere los ejemplos y luego comente sobre ellos.

▍To


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

▍Después


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

▍ Explicaciones


Si hablamos de legibilidad, de la complejidad del código o de los signos de código incorrecto, cuando el programa no utiliza las funciones de RxJS en su totalidad, esto indica que el desarrollador no conoce bien la API de RxJS. Si tocamos el tema del rendimiento, resulta que si el objeto observable necesita algo de tiempo para inicializarse, se suscribirá a firstObservable , entonces el sistema esperará a que se complete la operación, y solo después de eso comenzará el trabajo con el segundo objeto observable. Si estos objetos son solicitudes de red, se verá como una ejecución síncrona de solicitudes.

10. Sobre escribir


Siempre trate de declarar variables o constantes con un tipo diferente a any .

▍ Explicaciones


Si se declara una variable o constante en TypeScript sin especificar un tipo, el tipo se deducirá en función del valor que se le asigne. Esto puede llevar a problemas. Considere un ejemplo clásico de comportamiento del sistema en una situación similar:

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

Se supone que y es un número aquí, pero nuestro programa no lo sabe, por lo que muestra algo que parece incorrecto, pero no genera ningún mensaje de error. Se pueden evitar problemas similares asignando tipos apropiados a variables y constantes.

Reescribimos el ejemplo anterior:

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

Esto ayuda a evitar errores de tipo de datos.

Otra ventaja de un enfoque sistemático para escribir es que simplifica la refactorización y reduce la probabilidad de errores durante este proceso.

Considere un ejemplo:

 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 

Supongamos que queremos cambiar, en myFlashObject , el nombre de la propiedad loc a la location y cometimos un error myFlashObject editar el código:

 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 no se utiliza la myFlashObject al crear el objeto myFlashObject , en nuestro caso, el sistema supone que el valor de la propiedad loc de myFlashObject undefined está undefined . Ella no cree que loc pueda ser un nombre de propiedad no válido.

Si se usa la escritura al describir el objeto myFlashObject , en una situación similar veremos, al compilar el código, un maravilloso mensaje de error:

 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 está comenzando a trabajar en un nuevo proyecto, será útil establecer, en el archivo tsconfig.json , la opción strict:true para habilitar la verificación estricta de tipos.

11. Sobre el uso de linter


Tslint tiene varias reglas estándar como no-any , no-magic-numbers , no-console . El linter se puede personalizar editando el archivo tslint.json para organizar la verificación del código de acuerdo con ciertas reglas.

▍ Explicaciones


Usar un linter para verificar el código significa que si algo aparece en el código que está prohibido por las reglas, recibirá un mensaje de error. Esto contribuye a la uniformidad del código del proyecto, mejora su legibilidad. Vea otras reglas de tslint aquí.

Cabe señalar que algunas reglas incluyen medios para corregir lo que se considera inadmisible de acuerdo con ellas. Si es necesario, puede crear sus propias reglas. Si está interesado, eche un vistazo a este material, que analiza la creación de reglas personalizadas para el linter utilizando TSQuery .

▍To


 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 

▍Despué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. 

Resumen


Hoy revisamos 11 recomendaciones que esperamos sean útiles para los desarrolladores de Angular. La próxima vez, espera 11 consejos más.

Estimados lectores! ¿Utiliza el marco angular para desarrollar proyectos web?

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


All Articles