Чем отличается useLayoutEffect от useEffect в React?
Порядок выполнения в жизненном цикле
Оба хука принимают одинаковые аргументы — функцию-эффект и массив зависимостей — и запускаются после рендера. Разница в том, когда именно относительно отрисовки браузера.
useEffect (асинхронный)
- React обновляет виртуальный DOM и применяет мутации к реальному DOM.
- Браузер рисует обновлённый экран — пользователь видит изменения.
- Только после этого запускается колбэк
useEffect.
Это означает, что побочный эффект не блокирует отрисовку и не ухудшает воспринимаемую производительность.
useLayoutEffect (синхронный)
- React обновляет виртуальный DOM и применяет мутации к реальному DOM.
- Синхронно запускается колбэк
useLayoutEffect— браузер ещё не нарисовал кадр. - Только после завершения колбэка браузер рисует экран.
Если внутри эффекта изменить стили или DOM, пользователь никогда не увидит «промежуточное» состояние — всё произойдёт за один кадр.
Когда использовать useLayoutEffect
- Измерение DOM: получение
getBoundingClientRect,scrollHeight, позиции элементов. - Синхронная корректировка позиции: тултипы, всплывающие меню, которые нужно позиционировать относительно триггера.
- Предотвращение «мерцания»: когда
useEffectдаёт заметный визуальный скачок из-за двойного рендера.
// Пример: позиционирование тултипа без мерцания
const Tooltip = ({ targetRef }: { targetRef: RefObject<HTMLElement> }) => {
const tooltipRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
// Браузер ещё не отрисовал — безопасно читать и писать DOM
const targetRect = targetRef.current?.getBoundingClientRect();
if (targetRect && tooltipRef.current) {
tooltipRef.current.style.top = `${targetRect.bottom + 8}px`;
tooltipRef.current.style.left = `${targetRect.left}px`;
}
}, [targetRef]);
return <div ref={tooltipRef} className="tooltip">Подсказка</div>;
};
Когда использовать useEffect
В подавляющем большинстве случаев: запросы к API, подписки на события, таймеры, логирование — всё, что не требует чтения или синхронного изменения DOM до отрисовки.
// Стандартный случай — загрузка данных
useEffect(() => {
let cancelled = false;
fetchUser(userId).then((data) => {
if (!cancelled) setUser(data);
});
return () => { cancelled = true; };
}, [userId]);
SSR и предупреждения
useLayoutEffect не может выполниться на сервере (там нет DOM). Next.js и другие SSR-фреймворки выведут предупреждение: useLayoutEffect does nothing on the server. Чтобы избежать этого, используют один из паттернов:
- Заменить на
useEffect, если синхронность не критична. - Использовать кастомный хук
useIsomorphicLayoutEffect, который на сервере подменяетuseLayoutEffectнаuseEffect.
import { useEffect, useLayoutEffect } from 'react';
// Безопасный вариант для SSR
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
Итог
| useEffect | useLayoutEffect | |
|---|---|---|
| Момент запуска | После отрисовки браузера | До отрисовки браузера |
| Блокирует отрисовку | Нет | Да |
| Работает в SSR | Да | Нет |
| Типичный сценарий | Запросы, подписки | Измерение/коррекция DOM |
Что хочет услышать интервьюер
Чёткое понимание порядка: useLayoutEffect — до отрисовки, useEffect — после
Объяснение, почему useLayoutEffect предотвращает мерцание при DOM-манипуляциях
Знание об ограничениях useLayoutEffect в SSR и как их обходить
Конкретные примеры задач, где нужен useLayoutEffect (тултипы, измерения размеров)
Осознанный выбор: по умолчанию useEffect, useLayoutEffect — только когда необходимо
Пример: Мерцание с useEffect vs плавность с useLayoutEffect
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
// Плохо: пользователь увидит мерцание — сначала width=0, потом реальное значение
const WithEffect = () => {
const ref = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState(0);
useEffect(() => {
// Запускается ПОСЛЕ отрисовки — браузер уже показал width=0
setWidth(ref.current?.offsetWidth ?? 0);
}, []);
return <div ref={ref}>Ширина: {width}px</div>;
};
// Хорошо: ширина устанавливается ДО первой отрисовки — мерцания нет
const WithLayoutEffect = () => {
const ref = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
// Запускается ПЕРЕД отрисовкой — браузер видит уже правильное значение
setWidth(ref.current?.offsetWidth ?? 0);
}, []);
return <div ref={ref}>Ширина: {width}px</div>;
};
Пример: useIsomorphicLayoutEffect для SSR-совместимости
import { useEffect, useLayoutEffect } from 'react';
// На сервере window не существует, поэтому подменяем хук
export const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
// Использование — как обычный useLayoutEffect, но без SSR-предупреждений
const SafeComponent = () => {
useIsomorphicLayoutEffect(() => {
document.title = 'Страница загружена';
}, []);
return <div>Контент</div>;
};
Типичные ошибки
Использовать useLayoutEffect везде «на всякий случай», не понимая, что он блокирует отрисовку
Путать порядок: утверждать, что useLayoutEffect запускается до рендера (он запускается после мутаций DOM, но до paint)
Игнорировать SSR-предупреждение и не знать паттерн useIsomorphicLayoutEffect
Не понимать разницы между «мерцанием» и нормальным обновлением — необоснованно переходить на useLayoutEffect
Считать, что useLayoutEffect — замена useEffect для «более надёжного» выполнения кода


