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

Хуки useEffect и useLayoutEffect в React (и, соответственно, в React Native) используются для управления побочными эффектами внутри функциональных компонентов. Несмотря на то, что оба выполняют схожие задачи — позволяют выполнять код после рендера компонента — между ними есть ключевые различия во времени выполнения, влиянии на рендеринг, а также в типичных сценариях применения.

Общее понимание "эффектов" в React

Функциональные компоненты в React изначально не имели доступа к жизненному циклу компонентов, как в классовом API (componentDidMount, componentDidUpdate, componentWillUnmount). Хуки useEffect и useLayoutEffect добавлены для управления побочными действиями: подписками, запросами к API, анимациями, логированием, обновлением DOM и т. д.

Как работают useEffect и useLayoutEffect

useEffect

  • Выполняется после того, как изменения отрисованы на экране (DOM обновлён).

  • Работает асинхронно, не блокирует отрисовку интерфейса.

  • Подходит для: запросов к серверу, логирования, синхронизации данных, таймеров, подписок.

useEffect(() => {
console.log('Сработал useEffect');
}, \[\]);

useLayoutEffect

  • Выполняется синхронно сразу после рендера, но до того, как браузер отобразит изменения пользователю.

  • Позволяет измерять DOM и синхронно влиять на него до того, как пользователь его увидит.

  • Блокирует отрисовку, пока эффект не завершится.

  • Подходит для: точного измерения размеров элементов, принудительного скролла, анимаций на основе геометрии, позиционирования.

useLayoutEffect(() => {
console.log('Сработал useLayoutEffect');
}, \[\]);

Сравнение по ключевым параметрам

Параметр useEffect useLayoutEffect
Время выполнения После отрисовки на экране После рендера, но до отображения
--- --- ---
Синхронность Асинхронный Синхронный
--- --- ---
Блокирует ли рендер ❌ Нет ✅ Да
--- --- ---
Подходит для работы с DOM Только если не важна точность тайминга Для точных измерений, скроллов, позиций
--- --- ---
Производительность Лучше Медленнее, может тормозить интерфейс
--- --- ---
Основные сценарии API-запросы, подписки, логирование Прокрутка, измерения, UI-фиксы
--- --- ---

Пример различий

Сценарий: измерение ширины элемента и установка ширины другого элемента

const MyComponent = () => {
const refA = useRef();
const refB = useRef();
useEffect(() => {
const width = refA.current.offsetWidth;
refB.current.style.width = \`${width}px\`; // может "прыгнуть"
}, \[\]);
return (
<View>
<Text ref={refA}>Измеряемый текст</Text>
<Text ref={refB}>Растягиваемый текст</Text>
</View>
);
};

Здесь useEffect выполнится после того, как refA уже отрисован и пользователь может увидеть короткий момент, когда refB ещё не растянут. Это вызовет визуальный скачок (layout shift).

Исправление через useLayoutEffect

useLayoutEffect(() => {
const width = refA.current.offsetWidth;
refB.current.style.width = \`${width}px\`;
}, \[\]);

В этом случае refB получит нужную ширину до того, как произойдёт отрисовка, и пользователь увидит уже готовый результат.

Типичные случаи использования useEffect

  • Получение данных с сервера (fetch, axios)

  • Настройка подписок (например, WebSocket, события клавиатуры)

  • Установка таймеров, интервалов

  • Взаимодействие с AsyncStorage, Location, Permissions

  • Изменение глобального состояния (через Redux, Recoil, Context)

useEffect(() => {
const timer = setInterval(() => {
console.log('Тик');
}, 1000);
return () => clearInterval(timer); // очистка
}, \[\]);

Типичные случаи использования useLayoutEffect

  • Измерение размеров DOM-элементов

  • Программная прокрутка (scroll) до нужного элемента

  • Применение стилей до рендера (например, inline-стили для анимаций)

  • Управление фокусом (например, inputRef.focus() перед показом)

  • Тонкая настройка UI перед отрисовкой

useLayoutEffect(() => {
const { height } = someRef.current.getBoundingClientRect();
console.log('Высота элемента:', height);
}, \[\]);

Предупреждение о useLayoutEffect в SSR

React выдаёт предупреждение при использовании useLayoutEffect в среде серверного рендеринга (например, в Next.js), потому что на сервере нет DOM, и этот хук не имеет смысла. Альтернатива — использовать useEffect с проверкой окружения:

const isBrowser = typeof window !== 'undefined';
if (isBrowser) {
useLayoutEffect(() => {
// только в браузере
}, \[\]);
}

Совмещение useEffect и useLayoutEffect

В сложных компонентах можно использовать оба хука:

useLayoutEffect(() => {
// измерения или подготовка DOM
}, \[\]);
useEffect(() => {
// подписки, запросы, side-effects
}, \[\]);

Важно понимать, что useLayoutEffect всегда срабатывает раньше, чем useEffect.

React Native и layout-эффекты

В React Native также работает useLayoutEffect, но вместо DOM используется Yoga layout engine. Его полезно применять, если нужно измерить View, задать скролл в ScrollView, отобразить модальное окно после точного позиционирования и т. д.

Пример прокрутки до нижней части ScrollView после появления контента:

useLayoutEffect(() => {
scrollViewRef.current.scrollToEnd({ animated: true });
}, \[messages\]);

Основные различия с точки зрения разработки

Аспект useEffect useLayoutEffect
Безопасность для SSR ✅ безопасен ❌ может вызвать ошибку при рендере на сервере
--- --- ---
Порядок вызова После отрисовки До отрисовки
--- --- ---
Видимость пользователем Пользователь может увидеть промежуточный результат Пользователь увидит только итоговый результат
--- --- ---
Побочные эффекты (анимации, размеры) ❌ может привести к визуальным скачкам ✅ полностью под контролем
--- --- ---

Когда использовать какой хук

  • **Вы используете setTimeout, fetch, console.log и подписки → useEffect
    **
  • **Нужно измерить размер, установить фокус или прокрутку → useLayoutEffect
    **
  • **Появляются "прыгающие" элементы (layout shift) → использовать useLayoutEffect
    **
  • **Работа в SSR-среде → избегать useLayoutEffect
    **

Оба хука важны в арсенале разработчика на React и React Native. Понимание того, в каком порядке и в каком контексте они выполняются, критично для создания производительных, отзывчивых и визуально стабильных интерфейсов.