> В каком порядке отрабатывают логи и рендер при использовании useEffect и useLayoutEffect с пустым массивом зависимостей в React (React)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: ПСБ

Стек: React

> Пример ответа

Короткий ответ

useEffect с пустым массивом зависимостей запускает callback после того, как браузер отрисовал изменения на экране (после paint). useLayoutEffect запускается синхронно сразу после того, как React обновил DOM, но до того, как браузер выполнил paint. Таким образом, порядок: рендер → useLayoutEffect → paint → useEffect. Логи внутри useLayoutEffect выполняются до отрисовки, логи внутри useEffect - после.

Подробное объяснение

React выполняет рендер компонента, вычисляя новый виртуальный DOM. Затем происходит commit фаза, в ходе которой React синхронно обновляет реальный DOM. Сразу после этого, до того как браузер получит контроль для отрисовки (paint), React вызывает все useLayoutEffect колбэки. Это синхронный шаг - браузер не может начать отрисовку, пока все useLayoutEffect не завершатся. После завершения всех useLayoutEffect управление возвращается браузеру, он выполняет paint - отображает изменения на экране. Затем, асинхронно, в следующем тике event loop, React вызывает useEffect колбэки. Таким образом, логи в useLayoutEffect увидят актуальный DOM, но до того, как пользователь увидит изменения на экране. Логи в useEffect увидят DOM уже после того, как пользователь увидел обновление.

На практике

useLayoutEffect используется для операций, которые должны быть выполнены до того, как пользователь увидит экран, чтобы избежать визуальных мерцаний. Например, измерение DOM-элементов, синхронная анимация, чтение scroll position или установка стилей, которые должны быть применены до paint. useEffect подходит для побочных эффектов, не влияющих на визуальное восприятие: запросы к API, подписки на события, логирование, аналитика. Использование useLayoutEffect для таких задач может ухудшить производительность, так как он блокирует отрисовку.

Пример кода

JSX
import { useEffect, useLayoutEffect, useRef } from 'react';
function Component() {
const ref = useRef(null);
useLayoutEffect(() => {
// Этот лог выполнится до paint
console.log('useLayoutEffect: DOM обновлён, но ещё не отрисован');
// Можно безопасно читать layout
console.log('Высота элемента:', ref.current.offsetHeight);
}, []);
useEffect(() => {
// Этот лог выполнится после paint
console.log('useEffect: после отрисовки на экране');
}, []);
return <div ref={ref}>Hello</div>;
}

Как отвечать на собеседовании

Начни с чёткого разделения фаз: рендер, commit, paint. Укажи, что useLayoutEffect - синхронный и блокирующий, а useEffect - асинхронный и неблокирующий. Приведи примеры, когда каждый из них уместен. Подчеркни, что useLayoutEffect используется редко и только для операций, критичных к визуальному восприятию. Если спросят про производительность, объясни, что избыточное использование useLayoutEffect может замедлить initial render.

Что проверяет интервьюер

Интервьюер проверяет понимание жизненного цикла React-компонента, различие между синхронным и асинхронным выполнением эффектов, а также умение выбирать правильный хук в зависимости от задачи. Также оценивается знание того, как React взаимодействует с браузерным event loop и paint cycle.

Типичные ошибки

  • Утверждение, что useLayoutEffect выполняется до рендера - это неверно, он выполняется после обновления DOM, но до paint.
  • Использование useEffect для чтения layout свойств (например, offsetHeight) - это может привести к непредсказуемым значениям, так как браузер ещё не отрисовал изменения.
  • Использование useLayoutEffect для асинхронных операций (например, fetch) - это бессмысленно и вредно, так как блокирует отрисовку без необходимости.
  • Путаница с порядком: некоторые думают, что useEffect выполняется до paint, хотя на самом деле он выполняется после.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью