Задача: реализовать хук useFirstRender
export default function App(props) { const isFirstRender = useFirstRender(); if(isFirstRender) return null; return props.children }
useFirstRender - StackBlitz
Next generation frontend tooling. It's fast!
🎯 Зачем спрашивают
- Проверить понимание разницы между ref и state (перерендер vs сохранение значения между коммитами).
- Умение мыслить жизненным циклом (render → commit → effects).
📝 Ответ
Коротко (❗СПОЙЛЕР):
import { useEffect, useRef } from 'react'; export const useFirstRender = () => { const isFirstRender = useRef(true); useEffect(() => { isFirstRender.current = false; }, []); return isFirstRender.current; };
Подробнее:
Для нашей задачи нужно сохранить состояние/признак того, что рендер был первым. На ум приходят два способа:
useState
useRef(я надеюсь, что об этом способе вы помните)
Но
useState нам не подойдет, так как он вызовет перерендер, из-за чего мы проморгаем момент, когда значение переменной isFirstRender будет true .Схема
flowchart TD A[🔄 Компонент монтируется] --> B[📞 useFirstRender] B --> C[⚙️ useState true<br>isFirstRender = true] C --> D[✅ Вернуть: true] D --> E[🎨 Рендер 1:<br>isFirstRender = true] E --> F[⏰ useEffect после рендера] F --> G{Проверка isFirstRender} G -->|true| H[🔄 setIsFirstRenderfalse] H --> I[⚡ Вызван ре-рендер] I --> J[📞 useFirstRender снова] J --> K[⚙️ useState false<br>isFirstRender = false] K --> L[❌ Вернуть: false] L --> M[🎨 Рендер 2:<br>isFirstRender = false] E --> N[✅ Корректно:<br>Один рендер с true] M --> O[❌ Проблема:<br>Два рендера вместо одного<br>Потеря семантики 'первого рендера'] style E fill:#e8f5e8,color:#000 style N fill:#e8f5e8,color:#000 style M fill:#ffebee,color:#000 style O fill:#ffebee,color:#000
Нам поможет
useRef . Он позволит сохранить значение и не вызовет рендера.import { useEffect, useRef } from 'react'; export const useFirstRender = () => { const isFirstRender = useRef(true); useEffect(() => { isFirstRender.current = false; }, []); return isFirstRender.current; };
Схема
flowchart TD A[🔄 Компонент монтируется] --> B[📞 useFirstRender] B --> C[⚙️ useReftrue<br>isFirstRender.current = true] C --> D[✅ Вернуть: true] D --> E[🎨 Первый рендер<br>isFirstRender = true] E --> F[⏰ useEffect после рендера] F --> G[✏️ Установить false<br>isFirstRender.current = false] G --> H[🔄 Изменение без ререндера] H --> I[📞 Следующий вызов useFirstRender] I --> J[⚙️ Текущее значение: false] J --> K[❌ Вернуть: false] K --> L[🎨 Следующие рендеры<br>isFirstRender = false] E --> M[👍 Корректное поведение<br>Один рендер с true] L --> N[👍 Ожидаемый результат<br>Последующие false] style E fill:#e8f5e8,color:#000 style M fill:#e8f5e8,color:#000 style L fill:#e8f5e8,color:#000 style N fill:#e8f5e8,color:#000
StrictMode в React 18 (dev-режим)
В
StrictMode React монтирует, размонтирует и снова монтирует компонент в деве, чтобы поймать сайд-эффекты.Поэтому хук при каждом новом монтировании снова вернёт
true (ожидаемо и нормально для каждого монтирования).⚖️ Компромиссы
- При SSR скрытие первого рендера может вызвать гидрационный mismatch/мерцание.
- Если требуется снять флаг до измерений layout, рассмотреть
useLayoutEffect(но учесть SSR нюанс).
🔎 Встречные вопросы
- Чем отличается «первый рендер» от «первого коммита»? Что будет, если React выполнил рендер, но откатил его до коммита (конкарренси)?
🚩 Красные флаги
- Реализация через
useState, приводящая к лишнему ререндеру ради чисто «флаговой» информации.
- Мутация ref во время рендера (а не в эффекте).