22 dicas para um desenvolvedor Angular. Parte 1

O autor do artigo, a primeira parte da tradução que publicamos, diz que ele trabalha em um aplicativo Angular em larga escala no Trade Me há cerca de dois anos. Nos últimos anos, a equipe de desenvolvimento de aplicativos aprimorou constantemente o projeto, tanto em termos de qualidade quanto de desempenho do código.


Esta série de materiais se concentrará nas abordagens de desenvolvimento usadas pela equipe do Trade Me, que são expressas na forma de mais de duas dúzias de recomendações sobre tecnologias como Angular, TypeScript, RxJS e @ ngrx / store. Além disso, será dada alguma atenção às técnicas de programação universal que visam tornar o código do aplicativo mais limpo e preciso.

1. Sobre trackBy


Usando ngFor para percorrer matrizes em modelos, use essa construção com a função trackBy , que retorna um identificador exclusivo para cada elemento.

▍ Explicações


Quando a matriz muda, Angular renderiza novamente a árvore DOM inteira. Mas se você usar trackBy , o sistema saberá qual elemento foi alterado e fará alterações no DOM que se aplicam apenas a esse elemento específico. Detalhes sobre isso podem ser encontrados aqui .

▍Para


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

▍Depois


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

2. Palavras-chave const e let


Se você declarar uma variável cujo valor não planeja alterar, use a palavra-chave const .

▍ Explicações


O uso apropriado das palavras-chave let e const esclarece a intenção em relação ao uso de entidades declaradas usando-as. Além disso, essa abordagem facilita o reconhecimento de problemas causados ​​pela substituição acidental de valores constantes. Nessa situação, um erro de compilação é lançado. Além disso, melhora a legibilidade do código.

▍Para


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

▍Depois


 //  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 transporte


Ao trabalhar com o RxJS, use operadores em pipeline.

▍ Explicações


Os operadores transmitidos suportam o algoritmo de agitação de árvore, ou seja, quando são importados, apenas o código planejado para execução será incluído no projeto. Isso também facilita a identificação de instruções não utilizadas nos arquivos.

Observe que esta recomendação é relevante para a versão Angular 5.5 e superior.

▍Para


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

▍Depois


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

4. Isolamento de correções de API


Nem todas as APIs são completamente estáveis ​​e sem erros. Portanto, às vezes é necessário introduzir alguma lógica no código destinado a corrigir problemas de API. Em vez de colocar essa lógica em componentes onde as APIs corrigidas são usadas, seria melhor isolá-la em algum lugar, por exemplo, em um serviço e referir-se ao serviço correspondente, em vez da API problemática do componente.

▍ Explicações


A abordagem proposta permite manter as correções "mais próximas" da API, ou seja, o mais próximo possível do código a partir do qual as solicitações de rede são feitas. Como resultado, a quantidade de código do aplicativo que interage com APIs problemáticas é reduzida. Além disso, verifica-se que todas as correções estão em um único local, como resultado, será mais fácil trabalhar com elas. Se você precisar corrigir erros na API, será muito mais fácil fazer isso em um único arquivo do que espalhar essas correções pelo aplicativo. Isso facilita não apenas a criação de correções, mas também a busca pelo código apropriado no projeto e seu suporte.

Além disso, você pode criar suas próprias tags, como API_FIX (que se assemelha à tag TODO ), e sinalizar correções com elas. Isso facilita a localização dessas correções.

5. Assinatura no modelo


Evite assinar observáveis ​​de componentes. Em vez disso, assine-os em modelos.

▍ Explicações


Os operadores assíncronos em pipeline cancelam a assinatura automaticamente, o que simplifica o código, eliminando a necessidade de gerenciamento manual de assinaturas. Além disso, reduz o risco de o desenvolvedor esquecer de cancelar a assinatura do componente, o que pode levar a vazamentos de memória. É possível reduzir a probabilidade de vazamentos de memória usando regras linter destinadas a identificar objetos observáveis ​​dos quais eles não cancelaram a inscrição.

Além disso, a aplicação dessa abordagem leva ao fato de que os componentes deixam de ser componentes com estado, o que pode levar a erros quando os dados são alterados fora da assinatura.

▍Para


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

▍Depois


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

6. Removendo assinaturas


Ao assinar objetos monitorados, sempre verifique se as assinaturas foram excluídas corretamente usando operadores como take , takeUntil e assim por diante.

▍ Explicações


Se você não cancelar a inscrição do objeto observado, isso causará vazamentos de memória, pois o fluxo do objeto observado permanecerá aberto, o que é possível mesmo após a destruição do componente ou após o usuário ir para outra página do aplicativo.

Melhor ainda seria criar uma regra de linter para detectar objetos observados com uma assinatura válida.

▍Para


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

▍Depois


Use o operador takeUntil se desejar observar as alterações de um objeto até que outro objeto observado gere um determinado 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 assim é um padrão usado para controlar a remoção de assinaturas de muitos objetos observados em um componente.

Use take se precisar apenas do primeiro valor retornado pelo objeto observado:

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

Observe que aqui usamos takeUntil com take . Isso é feito para evitar vazamentos de memória causados ​​pelo fato de a assinatura não levar à obtenção do valor até a destruição do componente. Se a função takeUntil não fosse usada takeUntil , a assinatura existiria até o primeiro valor ser recebido, mas, como o componente já teria sido destruído, esse valor nunca teria sido recebido, o que levaria a um vazamento de memória.

7. Utilizando operadores adequados


Usando operadores de suavização com objetos observáveis, aplique os que correspondem aos recursos do problema que está sendo resolvido.

  • Use switchMap quando precisar ignorar a ação agendada anterior quando uma nova ação chegar.
  • Use o mergeMap caso precise processar todas as ações despachadas em paralelo.
  • Use concatMap quando as ações precisarem ser processadas uma após a outra, na ordem em que são recebidas.
  • Use o exhaustMap em situações em que, no processo de processamento de ações recebidas anteriormente, é necessário ignorar novas.

Detalhes sobre isso podem ser encontrados aqui .

▍ Explicações


Usar, se possível, um operador, em vez de obter o mesmo efeito combinando vários operadores em uma cadeia, reduz a quantidade de código do aplicativo que precisa ser enviado ao usuário. O uso de um operador selecionado incorretamente pode levar ao comportamento incorreto do programa, pois diferentes operadores processam os objetos observados de maneira diferente.

8. Carregamento preguiçoso


Em seguida, sempre que possível, tente organizar o carregamento lento dos módulos da aplicação Angular. Essa técnica se resume ao fato de que algo carrega apenas se for usado. Por exemplo, um componente é carregado apenas quando precisa ser exibido.

▍ Explicações


O carregamento lento reduz o tamanho dos materiais do aplicativo que o usuário precisa baixar. Isso pode melhorar a velocidade de download do aplicativo devido ao fato de os módulos não utilizados não serem transferidos do servidor para os clientes.

▍Para


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

▍Depois


 // 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. Sobre assinaturas em outras assinaturas


Às vezes, para executar alguma ação, você pode precisar de dados de vários objetos observáveis. Em tal situação, evite criar assinaturas para esses objetos dentro dos blocos de subscribe de outros objetos observáveis. Em vez disso, use operadores adequados para encadear comandos. Entre esses operadores, pode-se notar withLatestFrom e combineLatest . Considere os exemplos e depois comente-os.

▍Para


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

▍Depois


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

▍ Explicações


Se falamos de legibilidade, da complexidade do código ou dos sinais de código incorreto, quando o programa não usa os recursos do RxJS ao máximo, isso significa que o desenvolvedor não está familiarizado com a API do RxJS. Se tocarmos no tópico desempenho, acontece que, se o objeto observado precisar de algum tempo para inicializar, ele firstObservable , o sistema aguardará a conclusão da operação e somente depois disso o trabalho com o segundo objeto observado será iniciado. Se esses objetos forem solicitações de rede, será semelhante à execução síncrona de solicitações.

10. Sobre digitação


Sempre tente declarar variáveis ​​ou constantes com um tipo diferente de any outro.

▍ Explicações


Se uma variável ou constante for declarada no TypeScript sem especificar um tipo, o tipo será inferido com base no valor atribuído a ela. Isso pode levar a problemas. Considere um exemplo clássico de comportamento do sistema em uma situação semelhante:

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

Supõe-se que y seja um número aqui, mas nosso programa não o conhece, portanto exibe algo que parece errado, mas não produz nenhuma mensagem de erro. Problemas semelhantes podem ser evitados atribuindo tipos apropriados a variáveis ​​e constantes.

Reescrevemos o exemplo acima:

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

Isso ajuda a evitar erros de tipo de dados.

Outra vantagem de uma abordagem sistemática da digitação é que ela simplifica a refatoração e reduz a probabilidade de erros durante esse processo.

Considere um exemplo:

 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 

Suponha que myFlashObject alterar, no myFlashObject , o nome da propriedade loc para location e cometi um erro myFlashObject editar o 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 

Se a digitação não for usada ao criar o objeto myFlashObject , no nosso caso, o sistema assume que o valor da propriedade loc de myFlashObject é undefined . Ela não acha que loc possa ser um nome de propriedade inválido.

Se a digitação for usada ao descrever o objeto myFlashObject , em uma situação semelhante, veremos, ao compilar o código, uma maravilhosa mensagem de erro:

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

Se você estiver iniciando o trabalho em um novo projeto, será útil definir, no arquivo tsconfig.json , a opção strict:true para ativar a verificação estrita de tipo.

11. Sobre o uso de linter


Tslint tem várias regras padrão, como nenhum , nenhum número mágico , nenhum console . O linter pode ser personalizado editando o arquivo tslint.json para organizar a verificação de código de acordo com certas regras.

▍ Explicações


Usar um linter para verificar o código significa que, se algo aparecer no código que é proibido pelas regras, você receberá uma mensagem de erro. Isso contribui para a uniformidade do código do projeto, melhora sua legibilidade. Veja outras regras tslint aqui.

Note-se que algumas regras incluem meios de corrigir o que é considerado inadmissível de acordo com elas. Se necessário, você pode criar suas próprias regras. Se você estiver interessado, dê uma olhada neste material, que discute a criação de regras personalizadas para o linter usando o TSQuery .

▍Para


 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 

▍Depois


 // 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. 

Sumário


Hoje revisamos 11 recomendações que, esperamos, serão úteis para os desenvolvedores Angular. Da próxima vez, aguarde mais 11 dicas.

Caros leitores! Você usa a estrutura Angular para desenvolver projetos da web?

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


All Articles