Skip to main content

Чому useEffect не гарантовано викликається одразу після commit?

1. Що робить commit-фаза

У React кожен цикл оновлення ділиться на дві великі частини:

ФазаЩо робитьМожна перервати?
Render PhaseReact обчислює, що потрібно змінити (нове Virtual DOM)Так
Commit PhaseReact застосовує зміни в реальний DOMНі

Після commit-фази DOM вже оновлений, екран перемальований, і React готовий викликати ефекти (useEffect, useLayoutEffect, componentDidMount тощо).

Але, важливо, не всі ефекти однакові за пріоритетом.


2. Різні типи ефектів

React розрізняє два типи побічних ефектів:

ТипХукКоли викликається
Синхронний (layout)useLayoutEffectодразу після commit, до відображення на екрані
Пасивний (passive)useEffectасинхронно, після відображення і браузерного paint-тіку

3. Чому useEffect() - асинхронний

Після commit React:

  1. Оновлює DOM.
  2. Повідомляє браузеру: "готово, можна перемалювати екран".
  3. Чекає, поки браузер закінчить paint.
  4. Тільки потім викликає всі "пасивні" ефекти (useEffect).

Це потрібно, щоб:

  • UI став видимим якомога швидше;
  • ефекти (мережеві запити, таймери, підписки тощо) не гальмували перемальовування;
  • браузер залишався чутливим (scroll, input, animation не блокуються).

Приклад (різниця між useLayoutEffect і useEffect)

javascript
useLayoutEffect(() => { console.log('useLayoutEffect'); // синхронно після оновлення DOM }); useEffect(() => { console.log('useEffect'); // асинхронно, після paint });

Лог у консолі:

javascript
render commit useLayoutEffect ← одразу після commit PAINT (браузер) useEffect ← після відображення

4. Чому це важливо в React 18 і concurrent rendering

У React 18 рендер став переривним і пріоритетним (Concurrent Rendering). Через це React може:

  • відкладати виклик useEffect(), якщо є більш термінові завдання (наприклад, користувач клікнув);
  • скасовувати ефекти, якщо рендер скасувався до commit;
  • об'єднувати кілька рендерів в один commit, а ефекти викликати тільки один раз після нього.

Тому React гарантує лише одне: useEffect() буде викликаний після того, як DOM вже оновлений і браузер показав зміни, але не обов'язково миттєво - це може бути на наступному event loop-тіку.


5. А якщо потрібен ефект "одразу після commit"?

Тоді потрібно використовувати useLayoutEffect():

javascript
useLayoutEffect(() => { console.log('Синхронно після commit'); });

useLayoutEffect:

  • викликається синхронно одразу після того, як React оновив DOM;
  • блокує відображення до завершення колбека;
  • ідеально підходить для вимірювань (наприклад, getBoundingClientRect).

Але використовувати useLayoutEffect без потреби небезпечно, він може затримувати відображення UI. Для більшості побічних ефектів (fetch, підписки, таймери) краще useEffect.


6. Візуально

javascript
Render phase Commit phase (DOM оновлено) useLayoutEffect (одразу) Браузер відобразив (paint) useEffect (асинхронно)

7. Підсумок

React не викликає useEffect одразу після commit, тому що useEffect - це пасивний ефект, який виконується асинхронно після того, як браузер завершив paint.

Це гарантує, що:

  • UI відобразиться максимально швидко,
  • ефекти не блокують інтерфейс,
  • concurrent rendering залишається чутливим.

ХукКоли викликаєтьсяВикористовувати для
useLayoutEffectодразу після commit (синхронно)вимірювання DOM, синхронізація layout
useEffectпісля paint (асинхронно)побічні ефекти, запити, підписки

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.