> Почему рефы устанавливаются раньше полного рендера в React (React)

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

Компании: Т-Банк

Стек: React

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

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

Рефы устанавливаются до завершения полного рендера, потому что React выполняет присвоение ref в фазе commit, сразу после применения изменений к DOM, но до вызова useEffect и layout эффектов. Это гарантирует, что ref.current будет доступен для чтения в эффектах и обработчиках событий, которые запускаются после рендера.

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

В React жизненный цикл рендера делится на две фазы: render и commit. Во время render фазы React вычисляет изменения виртуального DOM, но не применяет их к реальному DOM. В этой фазе ref-объекты уже созданы, но их current свойство ещё не установлено, так как соответствующие DOM-узлы ещё не существуют.

В commit фазе React применяет изменения к реальному DOM. Сразу после этого, но до вызова useEffect и useLayoutEffect, React устанавливает ref.current на соответствующие DOM-узлы. Это происходит в синхронном порядке, гарантируя, что к моменту запуска эффектов ref уже будет содержать актуальную ссылку.

Такое поведение необходимо для:

  • Доступа к DOM-узлам в useLayoutEffect без дополнительных проверок
  • Возможности использования ref в обработчиках событий, привязанных в эффектах
  • Корректной работы библиотек, которые требуют прямой доступ к DOM (например, D3, Three.js)

На практике

Это поведение критично при работе с:

  • Фокусом на элементах (autoFocus, управление фокусом)
  • Измерениями DOM-элементов (getBoundingClientRect, offsetHeight)
  • Интеграцией с библиотеками, управляющими DOM напрямую
  • Анимациями, которые стартуют сразу после монтирования

Важно помнить, что ref не обновляется при каждом рендере, если сам элемент не меняется. React оптимизирует это: если элемент с ref остаётся тем же, ref.current не перезаписывается.

Пример кода

JSX
function MeasureExample() {
const divRef = useRef(null);
const [height, setHeight] = useState(0);
// useLayoutEffect выполняется синхронно после commit фазы
// ref.current уже установлен на DOM-узел
useLayoutEffect(() => {
if (divRef.current) {
setHeight(divRef.current.offsetHeight);
}
}, []);
return (
<div ref={divRef}>
<p>Высота этого блока: {height}px</p>
</div>
);
}

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

Начни с чёткого разделения фаз render и commit. Объясни, что ref устанавливается в commit фазе, до вызова эффектов. Подчеркни, что это intentional design decision, а не баг. Упомяни, что callback refs (функция вместо объекта) вызываются после каждого рендера, когда элемент меняется, что даёт больше контроля. Если спросят про useImperativeHandle - свяжи с тем же механизмом.

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

  • Понимание жизненного цикла React (render vs commit)
  • Знание порядка выполнения: ref → useLayoutEffect → useEffect
  • Умение объяснить, почему это важно для практических задач
  • Понимание разницы между ref как объектом и callback ref
  • Осознание trade-off между синхронным доступом к DOM и производительностью

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

  • Утверждение, что ref устанавливается в render фазе - это неверно, так как DOM ещё не обновлён
  • Путаница между ref и state: ref не вызывает ререндер, даже если его current меняется
  • Ожидание, что ref.current будет доступен сразу после вызова setState - это не гарантировано до следующего commit
  • Использование ref.current в render методе без проверки на null - это антипаттерн, так как ref может быть не установлен
  • Забывание, что callback ref вызывается с null при размонтировании элемента

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

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