Ось готовий урок, створений спеціально для тебе у стилі David Malan (CS50).
🎓 Change Detection та OnPush Strategy: Мистецтво оптимізації
Привіт, друзі! 👋 Радий бачити вас на цьому занятті.
Сьогодні ми зазирнемо під капот того, що робить наші Angular-додатки "живими". Ми поговоримо про магію, яка перетворює ваші дані у JavaScript на пікселі на екрані. Ми поговоримо про Change Detection.
Але не просто про те, "як це працює". Ми розберемося, як змусити це працювати швидко.
1. 🔥 Вступ: Проблема «Параноїдального Офіціанта»
Уявіть, що ви прийшли у величезний ресторан. Там сотні столиків. І є один офіціант.
Як працює цей офіціант у стандартному режимі? Він підходить до кожного столика, кожні 3 секунди й питає: "Вам щось потрібно? А вам? А вам?". Навіть якщо ви просто сидите і п’єте воду, він все одно перевіряє вас.
А тепер уявіть, що на кухні (у вашому коді) сталася подія — хтось натиснув кнопку. Офіціант (Change Detection) кидає все і знову оббігає всі 100 столиків, щоб перевірити, чи не змінилося замовлення.
❓ Питання до вас: Чи ефективно це, якщо у ресторані 1000 столиків? Звісно, ні. Офіціант втомиться, сервіс стане повільним, клієнти (користувачі) почнуть скаржитися на "лаги".
У світі Angular це називається Default Change Detection Strategy. Це надійно (ніхто не залишиться голодним), але це дорого для процесора.
Сьогодні ми навчимо нашого офіціанта бути розумним. Ми навчимо його підходити до столика тільки тоді, коли ви підняли руку або коли змінилася тарілка. Це і є OnPush Strategy.
2. 🧠 Теоретична база: Що відбувається під капотом?
Давайте розберемо це без складних академічних термінів.
Що таке Change Detection (CD)?
Це механізм, який синхронізує Model (ваші змінні в class Component) з View (HTML-шаблоном).
В Angular це працює зверху вниз. Від кореневого компонента (Root) до найглибших дітей.
Стратегія за замовчуванням (Default)
Angular "параноїдальний". Якщо десь у додатку стався клік, таймер (setTimeout) або прийшов запит з сервера — Angular думає: "Можливо, щось змінилося!" і перевіряє весь додаток. Кожен байдинг {{ variable }} звіряється.
Стратегія OnPush ⚡️
Коли ви вмикаєте ChangeDetectionStrategy.OnPush у компоненті, ви ніби укладаєте з Angular контракт.
Ви кажете: "Angular, не перевіряй мене, поки я не дам тобі знати".
Angular перевірить такий компонент (і його дітей) ТІЛЬКИ у трьох випадках:
- Змінилися
@Input()посилання (References). (Найважливіше!) - Подія виникла всередині цього компонента (наприклад, клік по кнопці в самому компоненті).
- Ви вручну попросили перевірку (через
AsyncPipeабоChangeDetectorRef).
🔑 Ключове поняття: Reference vs Value (Посилання vs Значення)
Це той момент, де новачки часто "ламаються". Увага сюди!
Уявіть поштову скриньку (змінна). * Якщо ви міняєте вміст листа всередині скриньки (мутація об'єкта) — скринька ззовні виглядає так само. Angular з OnPush не побачить змін. * Якщо ви замінюєте скриньку на нову (зміна посилання) — Angular бачить: "О, це щось нове!" і оновлює View.
Запам’ятайте:
OnPushпрацює через порівняння посилань (oldObject === newObject).
3. 🧪 Приклади: Від простого до реального
Приклад 1: Проблема мутації (Типова помилка)
У нас є компонент картки користувача з OnPush.
@Component({
selector: 'user-card',
template: `<h1>{{ user.name }}</h1>`,
changeDetection: ChangeDetectionStrategy.OnPush // 👈 Увімкнули режим економії
})
export class UserCardComponent {
@Input() user: { name: string };
}
А ось батьківський компонент, який передає дані:
@Component({
template: `
<user-card [user]="currentUser"></user-card>
<button (click)="changeName()">Змінити ім'я</button>
`
})
export class ParentComponent {
currentUser = { name: 'David' };
changeName() {
// ❌ ПОМИЛКА: Ми мутуємо об'єкт!
this.currentUser.name = 'David Malan';
}
}
❓ Що ви побачите на екрані після кліку? Ви очікуєте побачити "David Malan"? Реальність: Ви побачите старе ім'я "David".
Чому? Тому що посилання на об'єкт currentUser залишилося тим самим. Angular (OnPush) подивився на вхідні дані, сказав: "Це той самий об'єкт у пам'яті, що й раніше. Я пішов спати" — і не оновив шаблон.
Приклад 2: Виправлення через Immutability (Незмінність)
Як ми думаємо, як профі? Ми не змінюємо старе, ми створюємо нове!
// ✅ ПРАВИЛЬНО: Створюємо нове посилання
changeName() {
// Використовуємо Spread operator (...)
this.currentUser = { ...this.currentUser, name: 'David Malan' };
}
Тепер currentUser — це новий об'єкт у новій комірці пам'яті. Angular бачить: "Посилання змінилося! Треба перемалювати компонент user-card". Вуаля!
4. 🛠 Практична частина
Прийшов час закотити рукави! Виконайте ці завдання, щоб закріпити матеріал.
Завдання 1: "Зламаний лічильник"
Створіть компонент CounterComponent з OnPush. Передайте йому об'єкт config = { count: 0 }. Спробуйте збільшити count мутацією (config.count++). Переконайтеся, що на екрані нічого не змінюється. Відчуйте цей біль.
Завдання 2: "Лікування незмінністю"
Виправте код із Завдання 1, використовуючи Spread operator або метод Object.assign, щоб створювати новий об'єкт при кожному кліку.
Завдання 3: "Масиви — це теж об'єкти"
У вас є список завдань (Array). Спробуйте додати завдання через push().
* Чому OnPush не реагує?
* Виправте це, використовуючи конструкцію this.items = [...this.items, newItem].
Завдання 4: "Магія AsyncPipe"
(Рівень PRO) Замість передачі об'єкта, передайте в компонент Observable<User>. Використайте AsyncPipe у шаблоні ({{ user$ | async }}).
Переконайтеся, що OnPush чудово працює з AsyncPipe без жодних додаткових танців з бубном.
Питання "А що, якщо...":
А що, якщо в компоненті з OnPush я натисну кнопку, яка просто робить console.log? Чи перевірить Angular шаблон?
(Відповідь: Так, бо події DOM всередині самого компонента автоматично позначають його як "брудний" для перевірки).
5. 💡 Мислення як у розробника
Як досвідчений сеньйор-розробник дивиться на Change Detection?
-
OnPush за замовчуванням. Багато команд (і я теж) вмикають
OnPushодразу при створенні компонента. Це змушує писати чистіший код і уникати непередбачуваних мутацій даних. -
Immutability — наш найкращий друг. Ми не просто "фіксимо баги", ми змінюємо стиль мислення. Замість "змінити це поле", ми думаємо "отримати новий стан на основі старого". Це робить код передбачуваним.
-
Обережно з методами у шаблоні. Якщо у вас Default Strategy, метод
{{ calculateTotal() }}у шаблоні буде викликатися сотні разів. ЗOnPush— тільки при реальних змінах. Це колосальна різниця в продуктивності для важких обчислень.
Типова пастка:
Використання бібліотек, які мутують дані (старі версії деяких бібліотек графіків або таблиць), разом з OnPush. Там доведеться викликати ChangeDetectorRef.markForCheck() вручну.
6. 🧩 Підсумок
Отже, друзі, що ми сьогодні дізналися?
- Default Change Detection — це надійно, але повільно (перевіряє все підряд).
- OnPush — це оптимізація. Ми кажемо Angular перевіряти нас тільки при зміні вхідних даних (Input References).
- Мутації — зло для OnPush. Завжди створюємо нові копії об'єктів та масивів.
Тепер ви вмієте робити свої додатки не просто робочими, а блискавично швидкими. Ви розумієте ціну кожного циклу перевірки.
🔜 Тизер наступного уроку:
Ми побачили, як AsyncPipe класно дружить з OnPush. Але звідки беруться ці потоки даних? На наступному уроці ми пірнемо у світ RxJS: Observables та Subjects. Ми навчимося керувати часом і подіями, як справжні архітектори матриці!
А поки що — це був CS50... тобто, Change Detection! Успіхів у коді! 💻🚀