Pull to refresh

Лучшее из опыта создания чистых и быстрых Angular приложений

Reading time4 min
Views14K
На написание этой статьи сподвигли поиски JavaScript front-end разработчиков в свою компанию в Ставрополе. Т.к. длительное время не удавалось найти толкового программиста и тогда мы решили запустить программу стажировки с большим количеством обучающего материала по Angular & JS.

Это перевод статьи Vamsi Vempati про его опыт работы над крупномасштабным приложением для компании Trade Me, написанным на Angular.



На сегодняшний день я уже проработал пару лет над крупномасштабным приложением Angular в компании Trade Me. В течение последних нескольких лет наша команда дорабатывала наше приложение с точки зрения стандартов написания кода и производительности, чтобы привести его в максимально хорошее состояние.

В статье в общих чертах описываются методы, которые мы используем в нашем проекте. Текст по большей части связан с Angular, TypeScript, RxJs и @ngrx/store.

Кроме того, будут рассмотрены некоторые общие рекомендации по написанию кода, которые помогут сделать приложение более «чистым» и читаемым.

1) trackBy


При использовании ngFor для зацикливания массива в шаблонах, обращайтесь к функции trackBy, которая вернёт уникальный идентификатор для каждого элемента.

Почему?


При изменении массива Angular перерисовывает дерево DOM полностью. Но если использовать функцию trackBy, то Angular будет понимать, какой элемент изменился, а затем внесёт изменения в DOM только для этого конкретного элемента.

Примечание: более развёрнутое описание можно прочитать в статье Натанеля Базаля. [eng]

До


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


После



// в шаблоне
<li *ngFor="let item of items; trackBy: trackByFn">{{ item }}</li>
// в компоненте
trackByFn(index, item) {   
  return item.id; // unique id corresponding to the item
}

2) const или let?


При объявлении переменных используйте const, если они не будут присваиваться заново.

Зачем?


Использование let и const там, где это уместно делает причину объявления переменных более понятной. Это также может помочь в выявлении проблем, когда значение случайным образом переназначается на константу из-за ошибки времени компиляции. Это также улучшает читаемость кода.

До


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

После


// значение car не переназначено, поэтому можно его сделать константой (const)
const car = 'ludicrous car';
let myCar = `My ${car}`;
let yourCar = `Your ${car}`;
if (iHaveMoreThanOneCar) {
  myCar = `${myCar}s`;
}
if (youHaveMoreThanOneCar) {
  yourCar = `${youCar}s`;
}

3) Pipe-подобные операторы


Используйте pipe-подобные операторы во время работы с операторами RxJs.
Pipe-подобные операторы включают в себя при импорте только тот код, который нужно выполнить.

Это также упрощает поиски неиспользуемых операторов в файлах.

Примечание: нужен Angular 5.5 и выше.

До


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

После


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

4) Подписывайте в шаблоне


Избегайте подписки на наблюдаемые объекты из компонентов — вместо этого подписывайтесь на них из шаблона.

Почему?


Каналы async автоматически отписываются, упрощая код и устраняя необходимость в ручном управлении подписками. Также снижается риск случайной отписки в компоненте, что выразится в утечке в памяти. Это исправимо с помощью правила lint для обнаружения неподписанных наблюдаемых объектах.

Кроме того, это не позволяет компонентам оставаться статичными и добавляет ошибки, когда данные изменяются за пределами подписки.

До


// шаблон
<p>{{ textToDisplay }}</p>
// компонент
iAmAnObservable
   .pipe(
      map(value => value.item),
      takeUntil(this._destroyed$)
    )
   .subscribe(item => this.textToDisplay = item);

После


// шаблон
<p>{{ textToDisplay$ | async }}</p>
// компонент
this.textToDisplay$ = iAmAnObservable
   .pipe(
      map(value => value.item)
    );

5) Очищайте подписки


При подписке на наблюдаемые объекты всегда проверяйте, что вы затем отпишетесь от них соответствующим образом, используя такие операторы, как take, takeUntil и пр.

Почему?


Ошибки отписки от наблюдаемых объектов приведёт к нежелательной утечке памяти, поскольку наблюдаемый поток остаётся открытым, вероятно, даже после удаления компонента или при переходе пользователя на другую страницу.

Более того, создайте правило lint для обнаружения наблюдаемых объектов, от которых нет отписки.

До


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


После


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();
   this._destroyed$.complete();
}

Использование takeUntil для прослушивания изменений до тех пор, пока другой наблюдаемый объект выдаёт значение:

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

Обратите внимание на использование takeUntil вместе с take в примере выше. Это позволяет избежать утечек в памяти, вызванных тем, что к подписке не было присвоено значение до того, как компонент был удалён.

Без takeUntil подписка всё равно будет в как бы подвешенном состоянии, пока не получит своё первое значение, но так как компонент уже был удалён, то она никогда не получит значение, а это приведёт к утечке в памяти.

6) Ленивая загрузка


По возможности старайтесь подгружать модули в приложении Angular только тогда, когда они используются.

Почему?


Это уменьшит размер загружаемого приложения и сократит его время загрузки.

До


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

После


// 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 {}

Продолжение перевода в следующей статье. Если кому не хочется ждать, то здесь ссылка на оригинал статьи.
Tags:
Hubs:
Total votes 21: ↑16 and ↓5+11
Comments6

Articles