Почему рефы устанавливаются раньше полного рендера в React
Что проверяют
Различаете ли вы фазу рендера и фазу коммита и понимаете ли, с какого момента ref.current уже содержит узел. Это проверка на понимание того, что функция компонента и работа с настоящим DOM происходят в разное время.
Как отвечать
Рендер в React это две разные вещи. Фаза рендера это вызов функции компонента: он должен быть чистым, React вправе его прервать, выбросить результат и запустить заново, поэтому узлов там ещё нет и ref.current трогать нельзя. Фаза коммита это применение посчитанных изменений к настоящему DOM, она идёт целиком и без прерываний.
Рефы привязываются именно в коммите, сразу после вставки узлов в документ и до того, как браузер нарисовал кадр. Порядок такой: React меняет DOM, отвязывает старые рефы (в них кладётся null), привязывает новые, вызывает useLayoutEffect и componentDidMount, и только потом отпускает управление на отрисовку. Обычный useEffect выполняется уже после показа кадра.
Поэтому «раньше полного рендера» означает: к моменту первого же эффекта узел в рефе есть. Это и позволяет измерить элемент и синхронно поправить разметку, не показав пользователю промежуточное состояние.
const box = useRef(null);
useLayoutEffect(() => {
// узел уже в DOM, кадр ещё не нарисован
setWidth(box.current.getBoundingClientRect().width);
}, []);
return <div ref={box} />;Где спотыкаются
Читают ref.current прямо в теле компонента. На первом рендере там null, и код либо падает, либо молча берёт устаревшее значение: обращаться к рефу законно только в обработчиках и эффектах.
Ждут, что запись в ref.current перерисует компонент. Реф это изменяемая ячейка вне реактивности, изменение не вызывает рендер, и для того, что видно на экране, нужен state.
Меряют размеры в useEffect вместо useLayoutEffect и получают заметный скачок: замер идёт после отрисовки. Рядом живёт вторая ловушка, ref в виде инлайновой функции пересоздаётся каждый рендер, поэтому React зовёт его сначала с null, а потом с узлом.