Разбери, может ли useState принять callback: в каких случаях и от чего это зависит.
Хуки
useState «принимает callback» в двух разных смыслах — и оба легальны, но работают по-разному: ленивый инициализатор при создании и функциональное обновление в сеттере.
Случай 1: функция как начальное значение (lazy initializer)
const [data, setData] = useState(() => JSON.parse(localStorage.getItem('draft')));Если передать в useState функцию, React вызовет её один раз при монтировании и возьмёт результат как начальное значение. Когда это нужно: дорогая инициализация (парсинг, чтение localStorage, построение структуры) — без ленивой формы выражение вычислялось бы на каждом рендере (результат бы игнорировался, но CPU тратился).
useState(compute()); // compute() выполняется КАЖДЫЙ рендер
useState(() => compute()); // compute() выполняется один разСлучай 2: функция в сеттере (functional update)
setCount((prev) => prev + 1);Сеттер с функцией применяет её к актуальному предыдущему значению из очереди обновлений. Когда это нужно:
- несколько обновлений подряд: два
setCount((c) => c + 1)дадут +2, а дваsetCount(count + 1)— только +1 (оба замкнули старый count); - обновление из колбэков с устаревшим замыканием (таймеры, подписки с
[]).
Чего useState НЕ умеет
Хранить функцию «как есть» напрямую нельзя — она будет истолкована как инициализатор/апдейтер. Функцию-значение оборачивают:
const [handler, setHandler] = useState(() => defaultFn); // хранение функции
setHandler(() => newFn);И: колбэка «после обновления» (как второй аргумент this.setState в классах) у useState нет — реагировать на изменение нужно через useEffect.
Коротко для собеседования
Да, в двух формах: useState(() => init) — ленивый инициализатор, вызывается один раз (для дорогой инициализации); setX((prev) => next) — функциональное обновление от актуального значения (для серий обновлений и устаревших замыканий). Хранить функцию — только через обёртку; колбэка «после setState» нет — это делает useEffect.