Чекліст чистого коду на Angular

5 хв. читання

Angular швидко здобув популярність фреймворка для кросплатформених веб-застосунків. В Angular багато вбудованих фіч, на зразок системи роутингу, впровадження залежностей, обробки форм тощо. Angular також змушує вас використовувати TypeScript та RxJS, тому що вони — частина екосистеми. З таким потужним набором інструментів цей фреймворк стає чудовим рішенням для корпоративних проектів.

Однак, вам буде складно засвоїти Angular, якщо це ваш перший JavaScript-фреймворк. Щоб трохи полегшити вам життя, у статті наведено чекліст для чистого коду на Angular.

Передісторія

З досвідом, структура та організація коду стають усе важливішими. Ми хочемо, щоб наш код був читабельним, розширюваним, підтримуваним, продуктивним, а його налагодження займало мінімум часу.

Пишіть код так, ніби той, хто буде його підтримувати, жорстокий психопат, який знає, де ви живете.

Програмісти — автори, а інші розробники — їх цільова аудиторія. Спроби зрозуміти чужий код можуть займати багато часу. Слід свідомо покращувати якість коду, щоб створювати застосунки, які будуть успішно підтримуватись.

Поради щодо стилю

Логічно почати з найкращих практик організації коду в Angular. Там наведені поради щодо синтаксису, конвенцій та структури застосунку з поясненням чому варто їх використовувати.

Angular CLI

Angular CLI — чудовий інструмент для роботи з застосунками на Angular. Він автоматизує виснажливу ручну роботу і значно покращує продуктивність. Лише декілька команд дадуть вам змогу:

  • Створити проект з нуля;
  • Використовувати компоненти, директиви та сервіси;
  • Аналізувати код;
  • Обслуговувати застосунок;
  • Запускати юніт- тестування та наскрізне-тестування.

Структура тек

З розширенням застосунку варто передбачити структуру, що допоможе керувати вашою кодовою базою. Важливо бути послідовним та узгодити структуру з командою.

Читабельний код

Якщо ви хочете, щоб рефакторинг був максимально ефективним, важливо мати читабельний код. Його легше зрозуміти, а отже легше налагоджувати, обслуговувати й розширяти.

Назви файлів

Якщо ви додаєте нові файли, зверніть увагу на їх назви. Вони повинні бути логічними та описувати свої функції (розділяйте назви крапками).

|-- my-feature.component.ts
or
|-- my-service.service.ts

Назви функцій та змінних

Треба називати змінні та функції так, щоб інші розробники розуміли їх призначення. Використовуйте осмислені імена: тут ясність важливіша за стислість.

Це допоможе вам уникнути написання таких функцій:

function div(x, y)) {
 const val = x / y;
 return val;
}

Так краще:

function divide(divident, divisor) {
  const quotient = divident / divisor;
  return quotient;
}

Створюйте невеликі чисті функції

Функції для реалізації деякої бізнес-логіки повинні бути невеликими та чистими. Такі функції легше тестувати та підтримувати. Якщо ви помітили, що функція стає перевантаженою, пeремістіть частину логіки до іншої функції.

Треба уникати такого:

addOrUpdateData(data: Data, status: boolean) {
  if (status) {
    return this.http.post<Data>(url, data)
      .pipe(this.catchHttpErrors());
  }
  return this.http.put<Data>(`${url}/${data.id}`, data)
    .pipe(this.catchHttpErrors());
  }
}

Так краще:

addData(data: Data) {
  return this.http.post<Data>(url, data)
    .pipe(this.catchHttpErrors());
}
updateData(data: Data) {
  return this.http.put<Data>(`${url}/${data.id}`, data)
    .pipe(this.catchHttpErrors());
}

Позбувайтеся зайвого коду

Дуже важливо контролювати зайвий код. Чим більше ви ігноруєте невикористовуваний код, тим складніше вам буде позбавитись від нього у майбутньому.

Уникайте коментарів

Існують випадки коли без коментарів не обійтись, але треба використовувати їх з розумом. Коментарі не повинні компенсувати погану інформативність вашого коду. Важливо пам'ятати про оновлення коментарів після зміни коду, але краще витратити цей час на написання якіснішого коду. Неточні коментарі — гірше, ніж їх відсутність, або:

Код не збреше, на відміну від коментарів.

Треба уникати такого:

// перевірка здорова їжа чи ні
if (meal.calories < 1000 &&
    meal.hasVegetables) {
  ...
}

Так краще:

if (meal.isHealthy()) {
 ...
}

Поділ обов'язків

Angular створений на основі поділу обов'язків. Це шаблон проектування, що спрощує підтримку, розширення, тестування та повторне використання коду. Для цього ми інкапсулюємо й обмежуємо логіку компонентів. Розподіл обов'язків — основа написання чистого коду на Angular. Тут діють наступні правила:

  • Розділяйте застосунок на декілька модулів. Так проект стане більш організованим, підтримуваним, читабельним та повторно використовуваним, а ми зможемо застосовувати відкладене завантаження.
|-- modules
|    |-- home
|    |     |-- home.spec|module|component|scss||routing.module|.ts
|    |-- about
|    |     |-- about.spec|module|component|scss|routin.module|.ts
  • Якщо ми хочемо повторно використати бізнес-логіку в інших частинах нашого застосунку, слід створити сервіс. Сервіси — важлива частина Angular і чудовий спосіб повторно використати бізнес-логіку. Сервіси часто застосовуються для подій, пов'язаних з HTTP. Таким чином, ми можемо дізнатися де треба внести зміни.

Швидка порада! Ви можете створити API сервіс, який буде обробляти логіку, пов'язану з HTTP. Огляньте цей приклад на GitHub.

  • Вам слід створити у вашому застосунку щось на зразок «загальної системи» із загальними компонентами. Це зручно, якщо ви не хочете навантажувати компоненти зайвим кодом. В Angular ми не можемо імпортувати компоненти для різних модулів прямо, нам треба розмістити їх в shared-модулі.
// src/app/shared/components/reusable/resuable.component
...
export class ReusableComponent implements OnInit {
  @Input() public title: string;
  @Input() public body: string;
 
  @Output() public onButtonClick = new EventEmitter();
  constructor() { }
  ngOnInit() {}
  public buttonClick(){
    this.onButtonClick.emit('Button was clicked');
  }
}
--------------------------------------------------------------------
// Тепер ви можете використати цей компонент всередині
// потрібного вам компонента
// src/app/some/some.component
@Component({
  selector: 'app-some',
  template: `<app-reusable [title]="'Awesome title!'"
               [body]="'Lorem ipsum'"
               (onButtonClick)="buttonClick($event)>
             </app-reusable>`,
})
export class SomeComponent implements OnInit {
 
  @Output() public onButtonClick = new EventEmitter();
  constructor() {}
  ngOnInit() {}
  public buttonClick(e){
    console.log(e);
  }
}

Швидка порада! Ми можемо контролювати вміст HTML з батьківського компонента тегом ng-content.

  • Якщо кілька HTML-елементів мають однакову поведінку (наприклад, якийсь фрагмент тексту виділяється кліком), слід використовувати директиву атрибута. Директиви атрибутів дозволяють змінити поведінку чи вигляд HTML-елементу.

Використовуйте TypeScript

TypeScript — надмножина JavaScript, що забезпечує статичну типізацію, створення класів та інтерфейсів. TypeScript у поєднанні з Angular — це розширене автозаповнення, швидка навігація та рефакторинг.

Щоб отримати максимум користі від TypeScript:

  • Слід завжди використовувати інтерфейс для того, щоб клас реалізовував оголошені функції та властивості.
// .../burger.model.ts
export interface Burger {
  name: string;
  calories: number;
}
// .../show-burger.component.ts
this.burger: Burger;
  • Слід використовувати об'єднання типів та перетини. Це особливо зручно при роботі з даними з API.
export interface Burger {
  ...,
  bestBefore: string | Date;
}

Швидка порада! Завжди оголошуйте змінні та константи з визначеним типом. У строго типізованому коді менше помилок.

Використовуйте TSLint

Поліпшити процес розробки можна з TSLint. Це статичний аналізатор коду для перевірки відповідності коду на TypeScript правилам. З цими правилами ви одразу виявите, що зробили помилку.

Prettier з TSLint

Ви можете поєднувати TSLint з Prettier. Prettier — чудовий інструмент форматування коду, який аналізує ваш код і організовує його за певними правилами. Якщо ви об'єднаєте Prettier з TSLint, вам не потрібно буде підтримувати стиль коду вручну. За форматування відповідатиме Prettier, а про інше потурбується TSLint.

Аналізуйте код з Husky

Усі ці правила можуть з легкістю збити вас з пантелику. Ви можете забути виконати якісь команди перед запуском коду на production, що призведе до поганих наслідків. Тут на допомогу приходить Husky. З Husky ви можете виконувати власні скрипти до коміту проміжних файлів — і ваш код буде чистим та організованим.

RxJS в Angular

RxJS — бібліотека для реактивного програмування, що дає можливість працювати з асинхронними потоками даних. RxJS вбудована в Angular, тому можна отримати з неї максимум користі.

Pipeable operators

У RxJS 5.5 були представлені pipeable operators. Конвеєрний підхід до observable-композиції дозволяє імпортувати не все одразу, а оператор за оператором. Конвеєрні оператори дозволяють нам створювати власні оператори без наслідування Observable.prototype.

Тому можна уникнути такого коду:

const name = this.loadEmployees()
  .map(employee => employee.name)
  .catch(error => Rx.Observable.of(null));

Так краще:

const name = this.loadEmployees()
    .pipe(
      map(employee => employee.name),
      catchError(error => of(null))
    );

Підписка на шаблон

Уявіть ситуацію, коли нам треба підписатися на декілька потоків. Яким головним болем буде зіставляти кожну властивість з відповідним значенням та вручну відписуватись, коли компонент буде знищено.

З асинхронним конвеєром ми підписуємось на потік прямо всередині нашого шаблону, і нам не потрібно зберігати результат у проміжній властивості. Підписка скасовується, якщо компонент знищено, що спрощує обробку підписки та робить ваш код чистішим.

Тому можна уникнути такого коду:

@Component({
  ...
  template: `<items [items]="item">`
})
class AppComponent {
  items: Item[];
  constructor(private itemService: ItemService) {}
  ngOnInit() {
    this.loadItems()
      .pipe(
        map(items => this.items = items;
      ).subscribe();
  }
  loadItems(): Observable<Item[]> {
    return this.itemService.findItems();
  }
}

Так краще:

@Component({
    ...
    template: `<items [items]="items$ | async"></items>`
})
class AppComponent {
  items$: Observable<Item[]>;

  constructor(private itemService: ItemService) {}
  ngOnInit() {
    this.items = this.loadItems();
  }
  loadItems(): Observable<Item[]> {
    return this.itemService.findItems();
  }
}

Уникайте витоків пам'яті

З асинхронним конвеєром Angular піклується про відписку. Якщо ж ми робимо це власноруч — виникає безлад. Відмова від відписки призведе до витоків пам'яті, тому що спостережуваний потік залишається відкритим.

Рішенням буде поєднати нашу підписку з оператором takeUntil та використати subject, що передає значення при знищенні компонента. Наш observable-ланцюжок завершиться, і ми відпишемось від потоку.

Тому можна уникнути такого коду:

this.itemService.findItems()
  .pipe(
    map((items: Item[]) => items),
  ).subscribe()

Так краще:

 private unsubscribe$: Subject<void> = new Subject<void>();
  ...
   this.itemService.findItems()
    .pipe(
       map(value => value.item)
       takeUntil(this._destroyed$)
     )
    .subscribe();
  ...
  public ngOnDestroy(): void {
    this.unsubscribe$.complete();
    this.unsubscribe$.unsubscribe();
  }

Не використовуйте вкладені підписки

Існують ситуації, коли вам треба використати дані з декількох спостережуваних потоків. Тоді вам слід уникати вкладених підписок. Вони важкі у розумінні та призводять до непередбачуваних проблем. Замість цього слід використовувати ланцюжок з методів switchMap, forkJoin або combineLatest для скорочення коду.

Тому можна уникнути такого коду:

this.returnsObservable1(...)
  .subscribe(
    success => {
      this.returnsObservable2(...)
        .subscribe(
          success => {
            this.returnsObservable3(...)
              .subscribe(
                success => {
                   ...
                },

Так краще:

this.returnsObservable1(...)
  .pipe(
    flatMap(success => this.returnObservable2(...),
    flatMap(success => this.returnObservable3(...)
  )
  .subscribe(success => {...});

Швидка порада! Може виникнути плутанина з операторами при роботі з декількома потоками. Огляньте статтю, яка дещо прояснить.

Subject в RxJS

Subject поводить себе одночасно як observable та observer. Subjects дозволяють передавати дані в потік observable, тому ними часто зловживають. Особливо це характерно для новачків у RxJS. Щоб з'ясувати, в яких випадках використати Subject, оглянемо їх дeтальнішe:

  • З Subject ми можемо створити декілька підписок для єдиного спостерігача. Повідомляємо усіх спостерігачів у списку методом next(). Це основне застосування Subjects у RxJS.
  • Оскільки Subjects реалізують одночасно Observable та Observer, вони підходять для вхідних та вихідних подій.
  • У RxJS Subjects не можна використати повторно. Неможливо викликати метод next() для завершеного Subject.

Швидка порада! Існують також спеціалізовані типи subjects : async subjects, behavior subjects та replay subjects. Дізнайтеся більше за посиланням.

Скорочуємо імпорти з аліасами шляхів

Уявіть, що вам треба імпортувати щось глибоко приховане в ієрархії проeкту . Ви отримаєте схожий вираз:

import 'reusableComponent' from '../../../shared/components/reusable/reusable.component.ts';

Тепер уявіть, що вам треба перемістити цей файл. Ви будете змушені оновлювати такий імпорт у кожному файлі. Не дуже зручно, чи не так?

Ми можемо значно скоротити громіздкий імпорт з аліасами для посилання на наші файли:

import 'reusableComponent' from '@app/shared/components/reusable.component.ts';

Для цього необхідно додати baseUrl та бажані paths до файлу tsconfig.json:

{
  "compilerOptions": {
    ...
    "baseUrl": 'src',
    "paths": {
      "@app:": ['@app/*']
    }
  }
}

Імпорти частіше всього використовуються для компонентів та сервісів з модулів Core та Shared. Щоб не вказувати повний шлях до них, ми створимо декілька index.ts-файлів з експортом:

// src/app/shared/components/index.ts
export * from './reusable/reusable.component.ts';
// src/app/shared
export * from '/components';

Тепер можна посилатися на імпорт файлу так:

import 'ReusableComponent' from '@app/shared/';

Швидка порада! Свідомо використовуйте аліаси шляхів для імпорту сервісів чи компонентів всередині модулів Core та Shared. Це може призвести до «циклічних залежностей». У такому разі треба писати повний шлях.

Відкладене завантаження ваших модулів

Якщо ви використовуєте багатомодульну архітектуру, це рішення для вас. Синхронно повинен завантажуватись лише модуль, що відображає користувачеві увесь контент. Перевага відкладеного завантаження у тому, що модуль не завантажується, поки користувач не отримав доступ до маршруту. Так ми зменшимо загальний час запуску застосунку.

Управління станом

У великих та складних застосунках, з великим обсягом інформації з бази даних та обміном стану між декількома компонентами, подумайте про бібліотеку управління станом. З нею стани вашого застосунку будуть зосереджені в одному місці, тож зменшиться взаємодія між компонентами, і застосунок стане передбачуваним та зрозумілим.

Висновок

Angular – дуже потужний та функціональний фреймворк. Але легко розгубитися, якщо ви новачок у цій грі. Сподіваюсь, деякі принципи стали вам більш зрозумілими.

Немає чіткої схеми для створення чистого коду, але ключові моменти тут:

  • Пишіть читабельний код, який легко зрозуміти.
  • Поділ обов'язків. Інкапсулюйте та обмежуйте логіку компонентів, сервісів та директив. Кожен файл повинен бути відповідальним за щось одне.
  • Використовуйте TypeScript. Він забезпечує розширене автозаповнення, навігацію та рефакторинг.
  • Використовуйте TSLint. Він на місці перевіряє відповідність правилам коду на TypeScript. Поєднуйте цей інструмент з Prettier та Husky для максимальної користі.
  • RxJS в Angular. Отримуйте максимальну користь з цієї бібліотеки.
  • Скорочуйте імпорти з аліасами шляхів
  • Використовуйте відкладене завантаження модулів. Так ви зменшите час завантаження застосунку.
  • Управління станом (якщо необхідно). Це чудовий інструмент, але слід використовувати лише для масштабних та складних застосунків, де декілька компонентів поділяють однаковий стан.

Корисні ресурси з теми:

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.7K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація