Чем отличается useLayoutEffect от useEffect в React?

MiddleReact · Frontend·Обновлено 30 июля 2026
Коротко
useEffect выполняется асинхронно после того, как браузер отрисовал изменения, а useLayoutEffect — синхронно после мутаций DOM, но до отрисовки. Это позволяет useLayoutEffect читать размеры DOM и синхронно изменять вёрстку без видимого «мерцания».

Порядок выполнения в жизненном цикле

Оба хука принимают одинаковые аргументы — функцию-эффект и массив зависимостей — и запускаются после рендера. Разница в том, когда именно относительно отрисовки браузера.

useEffect (асинхронный)

  1. React обновляет виртуальный DOM и применяет мутации к реальному DOM.
  2. Браузер рисует обновлённый экран — пользователь видит изменения.
  3. Только после этого запускается колбэк useEffect.

Это означает, что побочный эффект не блокирует отрисовку и не ухудшает воспринимаемую производительность.

useLayoutEffect (синхронный)

  1. React обновляет виртуальный DOM и применяет мутации к реальному DOM.
  2. Синхронно запускается колбэк useLayoutEffect — браузер ещё не нарисовал кадр.
  3. Только после завершения колбэка браузер рисует экран.

Если внутри эффекта изменить стили или 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 для «более надёжного» выполнения кода

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее