Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Яка різниця між useEffect i useLayoutEffect?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**useEffect і useLayoutEffect**: `useEffect` запускається після того, як браузер намалював екран (асинхронно); `useLayoutEffect` запускається до paint, блокуючи браузер до завершення колбека (синхронно). ```jsx useEffect(() => { /* після paint */ }, [dep]); // API, таймери, підписки useLayoutEffect(() => { /* до paint */ }, [dep]); // DOM-вимірювання, виправлення мерехтіння ``` **Головне правило:** `useLayoutEffect` тільки для DOM-вимірювань або коли треба уникнути мерехтіння. Все інше - `useEffect`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**useEffect і useLayoutEffect** - різниця лише в таймінгу: один запускається після того, як браузер намалював екран, інший блокує малювання до завершення. ## Теорія ### TL;DR - `useEffect` спрацьовує після того, як браузер відмалював екран (асинхронно, після paint) - `useLayoutEffect` спрацьовує до paint, блокуючи браузер до завершення колбека (синхронно, до paint) - Аналогія: `useEffect` - це прибиральники, що приходять вже після того, як гості побачили безлад. `useLayoutEffect` прибирає до того, як гості зайшли - Потрібні DOM-вимірювання або виправлення без мерехтіння? `useLayoutEffect`. Все інше? `useEffect` - `useLayoutEffect` видає попередження в SSR (Node.js); `useEffect` ні ### Швидкий приклад ```jsx function Box() { const [count, setCount] = useState(0); const ref = useRef(); // Запускається ПІСЛЯ paint - юзер спочатку бачить стару ширину, потім стрибок useEffect(() => { ref.current.style.width = `${count * 20}px`; }, [count]); // Заміни на це - ширина оновиться до paint, без мерехтіння // useLayoutEffect(() => { // ref.current.style.width = `${count * 20}px`; // }, [count]); return ( <div ref={ref} style={{ background: '#eee' }} onClick={() => setCount(c => c + 1)}> Клік: {count} </div> ); } ``` З `useEffect` браузер спочатку малює стару ширину, потім ефект спрацьовує і елемент стрибає. Заміна на `useLayoutEffect` виправляє ширину ще до того, як браузер малює перший піксель. ### Головна різниця Обидва хуки мають однаковий синтаксис, cleanup-функція працює так само. Відрізняється тільки момент запуску всередині [фази commit у React](/questions/react-render-commit-phases). `useLayoutEffect` запускається синхронно після мутацій DOM, але ще до paint браузера. `useEffect` ставиться в чергу після paint з нижчим пріоритетом. Різниця в мілісекундах мала, але для ока помітна, коли потрібні DOM-вимірювання або виправлення стилів. Я бачив таку проблему в продакшені: тултіп, позиція якого вираховувалась у `useEffect`, помітно зіскакував на місце при кожному рендері. ### Коли що використовувати - Вимірювання DOM (ширина, висота, scroll position) перед рендером → `useLayoutEffect` - Виправлення макету щоб уникнути видимого стрибка або мерехтіння → `useLayoutEffect` - Анімації, що читають layout перед записом значень → `useLayoutEffect` - Запити до API, таймери, підписки на події → `useEffect` - Все, що не стосується видимого DOM-макету → `useEffect` ### Таблиця порівняння | Аспект | useEffect | useLayoutEffect | |---|---|---| | Таймінг | Після paint (асинхронно) | До paint (синхронно) | | Блокує paint браузера? | Ні | Так | | Читання DOM-layout | Викликає мерехтіння | Безпечно, відповідає реальному стану | | Ризик для продуктивності | Мінімальний | Може затримати рендер якщо колбек важкий | | SSR (Node.js) | Безпечно | Видає попередження | | Типове застосування | API, підписки, таймери | DOM-вимірювання, виправлення стилів | ### Як це працює всередині Під час фази commit у React колбеки `useLayoutEffect` запускаються синхронно відразу після мутацій DOM, до paint браузера. Колбеки `useEffect` ставляться в чергу після paint через внутрішній React Scheduler з нижчим пріоритетом. У React 18 з concurrent mode `useLayoutEffect` все одно залишається синхронним і блокує transitions. Для несрочної роботи всередині [startTransition](/questions/usetransition) використовуй `useEffect`. ### Типові помилки **Виправлення scroll у `useEffect`:** ```jsx // Неправильно: юзер спочатку бачить верх сторінки, потім стрибок useEffect(() => { ref.current.scrollTop = 100; }, []); // Правильно: прокрутка відбувається до першого paint useLayoutEffect(() => { ref.current.scrollTop = 100; }, []); ``` **Важкі обчислення в `useLayoutEffect`:** ```jsx // Неправильно: блокує paint на 100мс+, інтерфейс зависає useLayoutEffect(() => { for (let i = 0; i < 1_000_000; i++) { /* важка робота */ } }, []); ``` Колбек `useLayoutEffect` має виконуватись менше 5мс. Все важче переноси в `useEffect`. **Забуття про SSR:** `useLayoutEffect` видає попередження в Node.js: `Warning: useLayoutEffect does nothing on the server`. Якщо компонент рендериться на сервері (Next.js, Remix), переходь на `useEffect` або перевіряй `typeof window !== 'undefined'`. ### Де зустрічається в реальних проектах - Framer Motion читає DOM-layout у `useLayoutEffect` перед записом значень анімації - Next.js `<Image>` використовує `useLayoutEffect` для розміру placeholder - TanStack Query v5 використовує `useEffect` для запитів, `useLayoutEffect` для синхронізації кешу таблиць - React DevTools використовує `useLayoutEffect` для вимірювань в інспекторі - Redux Toolkit використовує `useEffect` для підписок на store ### Питання на співбесіді **Q:** Чи може `useLayoutEffect` погіршити продуктивність? **A:** Так, бо блокує paint. Якщо колбек займає більше кількох мілісекунд, браузерний фрейм затримується і інтерфейс підвисає. Chrome DevTools позначає це як long task. **Q:** Що відбувається з `useLayoutEffect` при SSR? **A:** React виводить попередження і хук нічого не робить на сервері. `useEffect` безпечний для SSR, бо виконується тільки в браузері. **Q:** Який порядок спрацювання cleanup? **A:** Обидва підтримують cleanup-функції. Cleanup у `useLayoutEffect` запускається синхронно до наступного layout-ефекту. У `useEffect` - асинхронно перед наступним ефектом. **Q:** У React 18 з concurrent mode, `useLayoutEffect` все ще блокує? **A:** Так, залишається синхронним і до paint навіть у concurrent mode. `startTransition` відкладає оновлення стану, але `useLayoutEffect` все одно спрацьовує до paint. Для несрочних побічних ефектів у transitions використовуй `useEffect`. ## Приклади ### Вимірювання висоти елемента перед paint ```jsx function TodoItem({ text, onDelete }) { const ref = useRef(); const [height, setHeight] = useState(0); useLayoutEffect(() => { // Вимірюємо до paint - без видимого стрибка макету setHeight(ref.current.getBoundingClientRect().height); }); return ( <div ref={ref} style={{ minHeight: `${height}px`, transition: 'height 0.2s' }}> {text} <button onClick={onDelete}>Видалити</button> </div> ); } ``` `getBoundingClientRect` читає реальні розміри DOM. В `useEffect` браузер спочатку намалював би неправильну висоту - видно стрибок при додаванні або видаленні елемента. `useLayoutEffect` вимірює і виправляє до того, як юзер щось побачить. ### Завантаження даних з useEffect ```jsx function UserProfile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { let cancelled = false; fetch(`/api/users/${userId}`) .then(res => res.json()) .then(data => { if (!cancelled) setUser(data); }); return () => { cancelled = true; }; }, [userId]); if (!user) return <p>Завантаження...</p>; return <p>{user.name}</p>; } ``` Завантаження даних не має відношення до pre-paint layout. `useEffect` тут правильний вибір. Прапорець `cancelled` запобігає оновленню стану розмонтованого компонента - поширене джерело попереджень про витік пам'яті в React DevTools.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.