Як React 18 змінив роботу життєвого циклу?
1. Головна ідея React 18 - Concurrent Rendering
До React 18:
- React рендерив компонент синхронно: якщо рендер почався, він ішов до кінця - не можна було призупинити, скасувати або об'єднати декілька оновлень.
- Всі життєві цикли викликалися в чітко передбачуваному порядку (монтування -> commit -> ефекти).
З React 18:
- Рендер став асинхронним і перериваним (через Fiber concurrent mode).
- React тепер може:
- призупиняти рендер,
- відкочувати незавершені зміни,
- повторно викликати ефекти,
- об'єднувати декілька state-оновлень в одну "сесію".
Це напряму впливає на виклики життєвого циклу і хуків, оскільки тепер ефекти можуть виконуватися більше одного разу в dev-режимі, а "монтування" може бути симульоване декілька разів.
2. Поведінка в Strict Mode (React 18)
Найпомітніша для розробників зміна:
React 18 тепер викликає
useEffect,useLayoutEffect,componentDidMount,componentWillUnmount- двічі (у DEV-режимі), щоб допомогти знайти "нечисті ефекти".
Це називається "Strict Mode double invocation" і відбувається тільки в dev,
щоб перевірити, що твої ефекти і очищення (cleanup) детерміновані і не залежать від порядку виклику.
Приклад:
useEffect(() => {
console.log('ефект');
return () => console.log('очищення');
}, []);У React 18 (у StrictMode) ти побачиш:
ефект
очищення
ефектЧому: React симулює демонтування і повторне монтування, щоб перевірити, що твій ефект коректно очищає за собою ресурси (таймери, слухачі, підписки).
Це не баг - це частина нової "concurrent safety check".
3. Зміна фаз життєвого циклу під капотом
Життєвий цикл тепер сприймається в рамках двох фаз Fiber:
| Фаза | Призначення | Методи/хуки |
|---|---|---|
| Render Phase | React обчислює, що потрібно оновити (може бути призупинена) | render(), useMemo, useCallback, shouldComponentUpdate |
| Commit Phase | React застосовує зміни до DOM (завжди синхронна) | componentDidMount, componentDidUpdate, useLayoutEffect, useEffect |
У React 18 Render Phase може:
- запускатися багаторазово (React може "підготувати" різні версії UI і потім вибрати одну);
- не доводитися до commit, якщо рендер скасований;
- виконуватися у фоні (background rendering).
4. Що конкретно змінилося в поведінці життєвих циклів
componentWillMount, componentWillUpdate, componentWillReceiveProps
Офіційно застаріли ще раніше (React 16) і повністю несумісні з concurrent mode - React 18 їх взагалі не викликає в concurrent рендері. Замість них - безпечні аналоги:
getDerivedStateFromPropsgetSnapshotBeforeUpdatecomponentDidUpdate
componentDidMount / useEffect / useLayoutEffect
- Все ще викликаються в commit phase, але React може викликати cleanup -> повторний ефект при переході між рендерами з різним пріоритетом.
- У dev-режимі (
StrictMode) виконуються двічі при монтуванні (див. вище). - У concurrent режимі React може скасувати рендер до commit, і тоді ефекти взагалі не будуть викликані (бо DOM не змінився).
componentDidUpdate
- Викликається тільки для тих компонентів, які реально зафіксовані (committed). Якщо рендер скасований (наприклад, при переході на новий стан) - метод не викликається.
componentWillUnmount / cleanup у useEffect
- Тепер React гарантовано викликає cleanup:
- при демонтуванні,
- перед новим ефектом,
- при скасованому рендері (у деяких випадках у dev-режимі).
- Це робить логіку ефектів більш передбачуваною і безпечною.
shouldComponentUpdate
- Поведінка не змінилася напряму, але React тепер може перервати або відкласти рендер, тому перевірка може відбуватися декілька разів перед коммітом.
5. Нові можливості React 18, що впливають на "життєвий цикл"
| Нова фіча | Що робить | Як впливає |
|---|---|---|
| Concurrent Rendering | Асинхронний, пріоритетний рендерінг | Методи можуть викликатися не один раз, ефекти можуть відкладатися |
| Automatic Batching | React тепер групує всі state-оновлення, навіть у промісах | Менше "зайвих" ререндерів, інша поведінка useEffect |
Transitions (startTransition) | Дозволяє розділяти "термінові" і "нетермінові" оновлення | Життєвий цикл низькопріоритетних оновлень може викликатися пізніше |
| useId, useSyncExternalStore, useInsertionEffect | Нові хуки для коректної роботи в concurrent режимі | Оптимізують доступ до DOM, стилів і зовнішніх даних |
6. Що змінилося для useEffect
| Поведінка | React 17 | React 18 |
|---|---|---|
| Ефект викликається один раз у dev | Так | Ні - двічі (Strict Mode) |
| Очищення викликається тільки при демонтуванні | Так | Ні - також перед повторним ефектом |
| Ефект може бути відкладений (deferred) | Ні | Так (через concurrent rendering) |
| Можливий "скасований" ефект (якщо commit не відбувся) | Ні | Так |
7. Головне, що потрібно пам'ятати
- React 18 не "ламає" життєвий цикл, а робить його більш точним, асинхронним і безпечним.
- У продакшні ефекти все ще викликаються один раз - "двічі" тільки в dev.
- Важливо писати чисті ефекти - без побічних дій у тілі компонента і без залежності від кількості викликів.
Коротка зведена таблиця
| Що змінилося | React < 18 | React 18 |
|---|---|---|
| Рендерінг | Синхронний | Асинхронний / пріоритетний |
| Життєвий цикл | Однопрохідний | Може бути призупинений / повторений |
Ефекти (useEffect) | Один виклик | Подвійний виклик у StrictMode (dev) |
Очищення (cleanup) | Тільки при unmount | Також при повторному монтуванні |
Старі методи (componentWill*) | Працюють (legacy mode) | Несумісні |
| State batching | Тільки всередині подій | Автоматично всюди |
| Нові хуки | - | useId, useSyncExternalStore, useInsertionEffect |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.