Каков порядок вывода в консоль в React-компоненте?
Что проверяют
Знаете ли вы, в какой момент React вызывает тело компонента, а в какой эффекты, и как это устроено для родителя и потомка. Обычно дают код с console.log в теле, в useEffect и в useLayoutEffect и просят предсказать вывод. Следом спрашивают, почему в разработке лог появляется дважды.
Как отвечать
Начните с двух фаз: render (React вызывает компоненты и вычисляет, что изменилось) и commit (изменения попадают в DOM, запускаются эффекты). Порядок для функционального компонента при монтировании:
- тело функции: лог в теле срабатывает при каждом рендере, раньше всех;
useLayoutEffect: после изменения DOM, но до отрисовки браузером, синхронно;useEffect: как правило, уже после отрисовки.
Для дерева: тела вызываются сверху вниз, сначала родитель, потом дети, а эффекты срабатывают снизу вверх, сначала у детей, потом у родителя. При обновлении снова вызывается тело, затем отрабатывают очистки прошлых эффектов и только потом новые эффекты.
Если в задаче классовый компонент, порядок такой: constructor, render, componentDidMount. Именно так, componentDidMount никогда не идёт раньше render. При обновлении сначала render, затем componentDidUpdate, при удалении componentWillUnmount.
Отдельно скажите про StrictMode: в режиме разработки React вызывает тело компонента дважды, а при монтировании прогоняет цикл эффект, очистка, эффект. В продакшн-сборке этого нет, так React помогает найти эффекты без корректной очистки.
Где спотыкаются
Ставят componentDidMount или useEffect раньше рендера. Эффект всегда идёт после того, как компонент отрендерен и попал в DOM.
Считают, что эффект родителя выполнится раньше эффекта ребёнка. На деле родитель считается смонтированным, только когда смонтированы все его дети.
Видят двойной лог в разработке и глушат его флагом в useRef вместо того, чтобы написать правильную очистку. Эффект обязан переживать повторный запуск без побочного вреда.