Чем отличается useEffect от useLayoutEffect? Когда стоит использовать каждый?
useEffect и useLayoutEffect — это хуки в React, которые позволяют выполнять побочные эффекты в функциональных компонентах. Оба выполняются после рендера, но есть важные различия в моменте запуска, блокировке отрисовки, влиянии на визуальное поведение, а также в производительности. Правильный выбор между ними зависит от специфики задачи.
useEffect: что это, когда вызывается
useEffect выполняется асинхронно после того, как браузер нарисует (paint) изменения в DOM. То есть:
-
Компонент рендерится.
-
DOM обновляется и отрисовывается браузером.
-
После этого вызывается useEffect.
useEffect(() => {
console.log('Выполняется после paint');
}, \[\]);
Характеристики:
-
Не блокирует отрисовку — интерфейс будет виден пользователю как можно раньше.
-
Подходит для:
-
Фетчинга данных (HTTP-запросы);
-
Подписки на события;
-
Таймеров;
-
Логирования;
-
Обновления состояния на основе асинхронных данных;
-
Интеграции с внешними библиотеками.
-
Пример:
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, \[\]);
useLayoutEffect: что это, когда вызывается
useLayoutEffect работает почти так же, как useEffect, но вызывается синхронно после всех изменений DOM, до того, как браузер нарисует (до "paint"), то есть:
-
Компонент рендерится.
-
DOM обновляется (узлы уже вставлены).
-
useLayoutEffect срабатывает до отрисовки на экране.
-
Только потом браузер делает paint.
useLayoutEffect(() => {
console.log('Выполняется до paint');
}, \[\]);
Характеристики:
-
Блокирует отрисовку — браузер ждёт, пока эффект завершится.
-
Подходит для:
-
Измерения размеров или положения DOM-элементов (getBoundingClientRect);
-
Синхронных манипуляций с DOM (например, scrollTo, focus, classList);
-
Предотвращения визуальных "рывков" или миганий;
-
Анимаций, синхронных с layout'ом.
-
Пример:
const ref = useRef(null);
useLayoutEffect(() => {
const rect = ref.current.getBoundingClientRect();
console.log('Ширина элемента:', rect.width);
}, \[\]);
Ключевое различие: тайминг выполнения
Хук | Когда вызывается | Блокирует отрисовку | Асинхронность |
---|---|---|---|
useEffect | После отрисовки (paint) | ❌ нет | ✅ да |
--- | --- | --- | --- |
useLayoutEffect | После изменения DOM, до отрисовки (before paint) | ✅ да | ❌ нет |
--- | --- | --- | --- |
Почему useLayoutEffect может быть опасен
Если использовать useLayoutEffect без необходимости, это может вызвать:
-
Замедление отрисовки;
-
"Тормоза" на слабых устройствах;
-
Проблемы с UX (например, когда нужно как можно быстрее показать интерфейс).
Поэтому React выводит предупреждение при использовании useLayoutEffect в серверном рендеринге (SSR) — там его нельзя применять, потому что нет доступа к DOM.
Пример с useLayoutEffect: предотвращение мигания
function Modal({ show }) {
const ref = useRef();
useLayoutEffect(() => {
if (show) {
ref.current.focus(); // Установить фокус до отображения
}
}, \[show\]);
return (
<div tabIndex={-1} ref={ref}>
Модальное окно
</div>
);
}
Если бы использовался useEffect, фокус сработал бы после показа — пользователь мог бы заметить "прыжок" курсора. С useLayoutEffect это происходит незаметно.
Когда useEffect может быть недостаточным
Иногда при использовании useEffect происходят визуальные артефакты:
-
Расширение/сжатие элементов после отрисовки;
-
Скролл, который применяется после того, как пользователь уже увидел скачок;
-
Применение классов с задержкой (мигающие анимации).
В этих случаях лучше использовать useLayoutEffect, чтобы изменения DOM были завершены до отрисовки.
Как работает последовательность выполнения эффектов
Если в компоненте есть оба:
useLayoutEffect(() => {
console.log('layout effect');
}, \[\]);
useEffect(() => {
console.log('normal effect');
}, \[\]);
Порядок выполнения:
-
useLayoutEffect вызывается сразу после мутации DOM.
-
Потом React продолжает рендер других компонентов.
-
Затем браузер делает paint (отрисовку).
-
После этого вызывается useEffect.
SSR и поведение
-
useEffect не вызывается на сервере (логично, нет браузера и DOM).
-
useLayoutEffect тоже не вызывается на сервере, и React может предупредить, что он используется в неподходящем месте.
-
Лучше избегать useLayoutEffect в компонентах, которые могут быть отрендерены на сервере — иначе можно получить ошибку в консоли.
Производительность
-
useEffect работает быстрее в целом, потому что не блокирует отрисовку.
-
useLayoutEffect может тормозить интерфейс, особенно если в нём тяжёлые вычисления.
Использовать useLayoutEffect стоит только тогда, когда нужен точный контроль над DOM перед отображением.
Правило по умолчанию
Используй useEffect по умолчанию.
Используй useLayoutEffect только если есть визуальные баги или необходимость в измерении DOM до отрисовки.