Angular IDPStrong Junior
🔥 0

Модуль 3 · урок 2 з 11

Default проти OnPush

Стратегія перевірки — це домовленість між тобою й Angular про те, коли компонент взагалі варто дивитись. За замовчуванням він перевіряється завжди. З OnPush — лише коли для цього є привід. Це прямий пункт із таблиці ліда й найпростіша оптимізація, яка існує в Angular.

Default: перевіряти все й завжди

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

OnPush: перевіряти, коли є привід

@Component({
  selector: 'app-task-card',
  changeDetection: ChangeDetectionStrategy.OnPush,
  // …
})

Компонент із OnPush перевіряється рівно в пʼяти випадках:

  1. Змінилось посилання на будь-який @Input() — саме посилання, а не вміст.
  2. Подія з самого компонента чи його шаблону: (click), (input), хост-слухач.
  3. async pipe у шаблоні отримав нове значення.
  4. Змінився сигнал, який читається в цьому шаблоні.
  5. Хтось явно викликав markForCheck().
ЦИКЛ ПЕРЕВІРКИ ПРИ ONPUSH App Sidebar · OnPush input не мінявся TaskList · OnPush прийшов новий масив Footer · OnPush input не мінявся TaskCard — перевірено пропущено цілком пропущено цілком Замість двохсот вузлів у цикл потрапило два. Це і є вся оптимізація.
Гілки, у яких нічого не змінилось, пропускаються повністю — разом з усіма нащадками. Тому OnPush на батькові вигідніший, ніж на десятьох листках.

Ключове слово — посилання

// ❌ дитина з OnPush нічого не помітить
this.tasks.push(newTask);
this.selected.status = 'done';

// ✅ нове посилання — дитина перевіриться
this.tasks = [...this.tasks, newTask];
this.selected = { ...this.selected, status: 'done' };

Це найчастіша причина «я поставив OnPush і все зламалось». Насправді нічого не зламалось: компонент чесно повідомив, що з його точки зору дані ті самі — бо посилання те саме. Наступний урок цілком про це.

Що з подіями

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <button (click)="count = count + 1">{{ count }}</button>
  `,
})
export class Counter {
  count = 0;   // звичайне поле, не сигнал
}

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

Як позначається «брудний» шлях

Коли компонент отримує привід перевіритись, Angular позначає не лише його, а весь ланцюжок до кореня. Інакше обхід просто не дійшов би до нього: батьки з OnPush зупинили б спуск раніше.

Практичний наслідок

Ставити OnPush вибірково майже безглуздо. Якщо батько лишився на Default, він однаково перевіряється щоразу — і гілка не пропускається. Тому правило просте: OnPush на всіх компонентах, з першого дня. І саме тому в модулі 2 ми ввімкнули ESLint-правило, яке про це нагадує.

Що дає OnPush на демо
  • Таблиця на тисячу рядків не тормозить при наведенні миші.
  • У профайлері видно кілька вузлів замість усього дерева.
  • Код стає передбачуваним: оновлення лише через нові обʼєкти.
  • Це прямий критерій із таблиці, який легко показати.
Що ламається, поки не звикнеш
  • Мутації масивів і обʼєктів перестають бути видимими.
  • Оновлення з setTimeout чи стороннього колбека не доходить до екрана.
  • Ручний subscribe без markForCheck нічого не малює.
  • Спокуса «полагодити» все через detectChanges() — про це урок 4.

Найкоротша відповідь на співбесіді

«OnPush означає, що компонент перевіряється не в кожному циклі, а лише коли змінилось посилання на input, прийшла подія з його шаблону, спрацював async pipe, змінився сигнал у шаблоні або хтось викликав markForCheck. Через це працювати треба з незмінними даними: не мутувати масив, а створювати новий.»