React Hooks: полный разбор useState, useEffect, useMemo, useCallback — что спрашивают на собеседовании
Глубокий гид по React Hooks для собеседований: useState, useEffect, useMemo, useCallback, частые ошибки, реальные вопросы интервью и практические задачи.
- 1. Введение
- 2. Что такое React Hooks
- 2.1 История появления (React 16.8)
- 2.2 Главные принципы Hooks
- 2.3 Почему нельзя вызывать хуки условно
- 3. useState — глубже, чем просто «переменная»
- 3.1 Что спрашивают на собеседовании
- 3.2 Как React хранит state внутри Fiber (концептуально)
- 3.3 Типичные ошибки
- 3.4 Реальный вопрос с интервью
- 4. useEffect — самая сложная тема на собеседовании
- 4.1 Lifecycle в функциональных компонентах
- 4.2 Как работает dependency array
- 4.3 Почему возникает бесконечный ререндер
- 4.4 Cleanup-функция
- 4.5 useEffect и Strict Mode
- 4.6 Частые вопросы на useEffect собеседование
- 4.7 Stale closure: почему effect видит «старое» значение
- 4.8 Race conditions в запросах и защита от них
- 5. useMemo — оптимизация вычислений
- 5.1 Что делает useMemo
- 5.2 Когда действительно нужен
- 5.3 Когда вреден
- 5.4 Вопросы по useMemo
- 6. useCallback — стабилизация функций
- 6.1 Что делает useCallback
- 6.2 useMemo vs useCallback
- 6.3 Почему функции ломают React.memo
- 6.4 Когда useCallback бесполезен
- 7. React.memo + Hooks — любимая комбинация интервьюеров
- 8. Самые частые ошибки на собеседовании
- 9. Как отвечать на интервью по хукам
- 10. Практический блок
- Задача 1. Найти баг в useEffect
- Задача 2. Оптимизировать список с useMemo
- Задача 3. Починить лишние ререндеры
- Задача 4. Объяснить бесконечный цикл
- Задача 5. Переписать код без лишних хуков
- 12. Заключение
React Hooks остаются самым частым блоком на frontend-интервью: для junior это база, для middle — глубина понимания механики рендера, зависимостей и производительности. На практике именно по хукам чаще всего отсеивают кандидатов: человек может писать UI, но «сыпется» на вопросах про dependency array, стабильность ссылок и лишние ререндеры.
В этой статье разберем React hooks вопросы в формате, который близок к реальному собеседованию: теория, код, типичные ошибки и объяснение «почему так работает внутри». Отдельно сделан большой раздел useEffect (самый частый интент вроде useEffect собеседование), а также блок useMemo vs useCallback с практикой оптимизации.
Если вам нужно react хуки объяснение простыми словами, но без потери технической точности — это как раз такой материал: от базы до middle-уровня аргументации.
Если хотите глубже разобрать именно механику рендеринга и триггеры перерисовок, откройте React rendering: когда компонент перерисовывается. А для фокусного разбора мемоизации с типичными ошибками посмотрите React.memo, useMemo и useCallback: оптимизация без магии.
Тренировка React Hooks перед собеседованием
Тренируйтесь на нашей платформе: реальные вопросы по хукам, фидбек по ответам и разбор ошибок уровня Junior/Middle.
1. Введение
Почему React Hooks — это примерно 70% вопросов на интервью? Потому что хуки лежат в центре современной React-разработки: управление состоянием, сайд-эффекты, оптимизация и архитектура компонент. Даже если в вакансии не пишут «нужен глубокий React performance», в живом разговоре почти всегда проверяют, понимаете ли вы:
- как и когда компонент ререндерится;
- почему dependency array может сломать логику;
- когда оптимизация уместна, а когда это premature optimization;
- как отличить корректный
useEffectот антипаттерна.
Для middle-позиции недостаточно «знаю синтаксис». Нужен причинно-следственный уровень: вы должны объяснить, почему поведение происходит, какие риски есть в edge-case, и как вы принимаете архитектурные решения.
Дальше пойдет структура, которая закрывает и теорию, и практику: история появления хуков, useState, useEffect, useMemo, useCallback, связь с React.memo, типовые interview questions hooks, задачи и FAQ.
2. Что такое React Hooks
2.1 История появления (React 16.8)
До React 16.8 основным способом работы с состоянием были классовые компоненты. У этого подхода были реальные проблемы:
- Логика одного сценария часто размазывалась по lifecycle-методам (
componentDidMount,componentDidUpdate,componentWillUnmount). - Переиспользование stateful-логики требовало HOC или render props, что создавало вложенность и усложняло чтение.
- Работа с
thisи биндингом методов создавала лишнюю когнитивную нагрузку.
Hooks решили это, добавив state и эффекты в функциональные компоненты. В результате логика стала группироваться «по смыслу», а не «по фазам жизненного цикла».
2.2 Главные принципы Hooks
- Хуки используются в функциональных компонентах.
- Хуки вызываются только на верхнем уровне.
- Хуки вызываются только внутри React-компонентов или кастомных хуков.
Эти правила не «про стиль», а про корректность внутренней модели React.
2.3 Почему нельзя вызывать хуки условно
React сопоставляет состояние хука не по имени переменной, а по порядку вызовов в компоненте. Если порядок меняется между рендерами, React начинает подставлять не то состояние не в тот хук.
Плохой пример:
function Profile({ isAdmin }: { isAdmin: boolean }) {
const [name, setName] = useState("");
if (isAdmin) {
useEffect(() => {
console.log("admin mode");
}, []);
}
const [age, setAge] = useState(18);
return <div>{name} {age}</div>;
}
Если isAdmin изменится, порядок хуков «съедет», и состояние станет неконсистентным. Отсюда правило: никаких условных вызовов хуков.
3. useState — глубже, чем просто «переменная»
3.1 Что спрашивают на собеседовании
Типичные вопросы:
- Как работает batching?
- Почему state кажется асинхронным?
- В чем разница между
setState(value)иsetState(prev => ...)? - Когда реально происходит ререндер?
Ключевая мысль: вызов setState ставит обновление в очередь. React может объединять несколько обновлений (batching), чтобы выполнить меньше рендеров.
3.2 Как React хранит state внутри Fiber (концептуально)
На уровне концепции у каждого компонента (Fiber-узла) есть связанный список/цепочка хуков. Каждый useState хранит:
- текущее значение;
- очередь pending-обновлений;
- ссылку на следующий хук.
Во время рендера React проходит хуки в порядке вызова и применяет очередь обновлений. Поэтому порядок вызова критичен.
3.3 Типичные ошибки
- Мутация объекта состояния:
// Плохо
user.name = "Max";
setUser(user);
Нужно создавать новую ссылку:
setUser((prev) => ({ ...prev, name: "Max" }));
- Зависимость от старого значения без функционального апдейта.
- Ожидание, что несколько
setStateподряд всегда отработают «по шагам», хотя React может батчить.
3.4 Реальный вопрос с интервью
Почему при двойном
setCount(count + 1)счетчик увеличивается только на 1?
Проблема в том, что оба вызова используют одно и то же «старое» значение count из текущего рендера.
setCount(count + 1);
setCount(count + 1); // снова то же значение
Правильный вариант:
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
Здесь каждое обновление вычисляется из актуального предыдущего состояния в очереди.
4. useEffect — самая сложная тема на собеседовании
4.1 Lifecycle в функциональных компонентах
Через useEffect можно выразить сценарии:
- mount: код при первом появлении компонента;
- update: код при изменении зависимостей;
- unmount: cleanup перед удалением компонента.
Важно: useEffect — это не «новый lifecycle API», а механизм синхронизации React-компонента с внешним миром.
4.2 Как работает dependency array
-
useEffect(fn)без массива: эффект запускается после каждого рендера. -
useEffect(fn, []): эффект запускается после первого рендера (в production), cleanup — при размонтировании. -
useEffect(fn, [value]): эффект запускается после первого рендера и затем каждый раз, когдаvalueизменился.
Ошибка большинства кандидатов — думать, что dependencies это «ручная оптимизация». На деле это декларация: от каких значений зависит эффект.
4.3 Почему возникает бесконечный ререндер
Пример:
useEffect(() => {
setCount(count + 1);
}, [count]);
Механика:
- Рендер с
count = 0. - Эффект вызывает
setCount(1). - Компонент ререндерится.
countизменился -> эффект снова запускается.- Цикл бесконечный.
Как избегать:
- Проверять условие перед
setState. - Использовать другой источник триггера.
- Переосмысливать, нужен ли effect вообще (часто вычисление можно сделать прямо в рендере или
useMemo).
4.4 Cleanup-функция
Cleanup вызывается:
- перед следующим выполнением эффекта (если зависимости изменились);
- при размонтировании компонента.
Пример с подпиской:
useEffect(() => {
const handler = () => console.log(window.innerWidth);
window.addEventListener("resize", handler);
return () => {
window.removeEventListener("resize", handler);
};
}, []);
Если cleanup не сделать, появляются утечки: старые обработчики остаются висеть и могут дублировать логику.
4.5 useEffect и Strict Mode
В dev-режиме React Strict Mode может вызывать эффекты дважды (mount -> cleanup -> mount) для проверки устойчивости побочных эффектов. Это не баг production-режима, а механизм выявления проблем:
- отсутствие cleanup;
- неидемпотентные побочные действия;
- скрытые побочные эффекты в рендере.
4.6 Частые вопросы на useEffect собеседование
В чем разница между useEffect и useLayoutEffect?
useEffect выполняется после paint, useLayoutEffect — синхронно после изменений DOM, но до paint. Второй нужен редко, в основном для измерений/синхронной правки layout.
Когда эффект выполняется относительно paint?
useEffect — после того, как браузер отрисовал кадр.
Можно ли делать async напрямую в эффекте?
Функция эффекта должна вернуть cleanup-функцию или undefined, а async всегда возвращает Promise. Поэтому напрямую писать useEffect(async () => {}) нельзя.
Корректный вариант:
useEffect(() => {
let canceled = false;
async function load() {
try {
const res = await fetch("/api/data");
const data = await res.json();
if (!canceled) {
// setState(data)
}
} catch (e) {
if (!canceled) {
// setError(e)
}
}
}
load();
return () => {
canceled = true;
};
}, []);
4.7 Stale closure: почему effect видит «старое» значение
Один из самых частых вопросов уровня middle: почему внутри колбэка или таймера значение состояния «устарело».
Пример:
useEffect(() => {
const id = setInterval(() => {
console.log(count); // может логировать старое значение
}, 1000);
return () => clearInterval(id);
}, []);
Здесь effect с пустым массивом зависимостей захватывает count из первого рендера. Решения зависят от задачи:
- добавить
countв dependencies и пересоздавать интервал; - использовать
refдля хранения актуального значения; - пересмотреть архитектуру, чтобы не полагаться на «долгоживущий» колбэк со старыми данными.
На интервью важно проговорить саму причину: не «React багует», а обычная механика замыканий JavaScript.
4.8 Race conditions в запросах и защита от них
Еще один практический кейс: пользователь быстро меняет фильтр, а ответы приходят в другом порядке. В итоге интерфейс может показать устаревшие данные.
Пример проблемы:
- Запрос A уходит первым.
- Запрос B уходит позже.
- B возвращается быстрее, UI обновляется.
- Потом приходит A и перетирает UI старой версией.
Типовые решения:
AbortControllerдля отмены старого запроса;- проверка флага актуальности в cleanup;
- библиотека data-fetching с кэшом и дедупликацией.
Пример с AbortController:
useEffect(() => {
const controller = new AbortController();
async function load() {
const res = await fetch(`/api/search?q=${query}`, { signal: controller.signal });
const data = await res.json();
setItems(data);
}
load().catch((e) => {
if (e.name !== "AbortError") {
setError("Ошибка загрузки");
}
});
return () => controller.abort();
}, [query]);
Такой ответ на собеседовании сразу показывает практическую зрелость и понимание react performance hooks в контексте сетевого слоя.
5. useMemo — оптимизация вычислений
5.1 Что делает useMemo
useMemo мемоизирует значение: React кэширует результат функции и пересчитывает его только при изменении зависимостей.
const filtered = useMemo(() => {
return products.filter((p) => p.price > minPrice);
}, [products, minPrice]);
5.2 Когда действительно нужен
- Есть тяжелое вычисление (фильтрация/сортировка больших списков).
- Нужно стабилизировать ссылку на значение, чтобы не триггерить дочерние компоненты без необходимости.
5.3 Когда вреден
- Микрооптимизация там, где расчеты дешевы.
- Усложнение кода ради «ощущения performance».
- Ошибочные зависимости, которые дают устаревшее значение.
5.4 Вопросы по useMemo
Можно ли убрать useMemo?
Да, если нет проблемы производительности и без него код проще.
Что если dependency не указать? Тогда мемоизация теряет смысл или ломается корректность данных.
useMemo — гарантия или подсказка React?
Практически это оптимизация, а не бизнес-гарантия. Нельзя строить критичную логику на предположении «useMemo обязательно сработает как кэш навсегда».
6. useCallback — стабилизация функций
6.1 Что делает useCallback
useCallback мемоизирует функцию и возвращает стабильную ссылку, пока зависимости не изменились.
const onSelect = useCallback((id: string) => {
setSelectedId(id);
}, []);
6.2 useMemo vs useCallback
| useMemo | useCallback |
|---|---|
| Возвращает значение | Возвращает функцию |
| Мемоизирует результат вычисления | Мемоизирует ссылку на функцию |
Ключевая формулировка для интервью: useCallback(fn, deps) эквивалентен useMemo(() => fn, deps).
6.3 Почему функции ломают React.memo
Если родитель на каждом рендере создает новую функцию, дочерний компонент в React.memo видит новый prop по ссылке и ререндерится.
const Child = React.memo(function Child({ onClick }: { onClick: () => void }) {
console.log("Child render");
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("click");
}, []);
return (
<>
<button onClick={() => setCount((c) => c + 1)}>{count}</button>
<Child onClick={handleClick} />
</>
);
}
6.4 Когда useCallback бесполезен
- Если дочерний компонент не мемоизирован.
- Если функция не уходит вниз по дереву как prop.
- Если ререндеры и так дешевы и не являются bottleneck.
7. React.memo + Hooks — любимая комбинация интервьюеров
Частый сценарий interview questions hooks: «Почему оптимизация не сработала, хотя добавили React.memo?»
Причины:
- В props передаются новые объекты/массивы/функции на каждый рендер.
- Ошибочные dependencies в
useMemo/useCallback. - Компонент ререндерится из-за изменения context.
React.memo делает shallow comparison props. Это означает: сравнение по ссылкам для объектов и функций. Если ссылка новая, мемо не спасет.
Паттерн работы:
React.memoдля тяжелого дочернего компонента.useMemoдля стабильных вычисляемых данных, которые передаются вниз.useCallbackдля стабильных обработчиков.- Проверка в profiler, что эффект реально есть.
Мини-чеклист перед оптимизацией:
- Есть ли измеримая проблема (время рендера, лаг интерфейса)?
- Понимаете ли вы, какой компонент действительно дорогой?
- Изменит ли
React.memoповедение с учетом текущих props? - Не станет ли код сложнее, чем выигрыш в миллисекундах?
Это важно для интентов оптимизация react и react performance hooks: хороший кандидат сначала измеряет, потом оптимизирует.
8. Самые частые ошибки на собеседовании
- Путают
useMemoиuseCallback. - Не понимают dependency array и «добавляют только чтобы ESLint замолчал».
- Думают, что
useEffect(() => {}, [])это просто полный аналогcomponentDidMount. - Мутируют state и удивляются некорректным рендерам.
- Вызывают хуки условно.
- Используют
useEffectдля вычислений, которые можно делать без side-effect.
Отдельно для SEO-интента лишние ререндеры react: главная причина лишних ререндеров — нестабильные ссылки и отсутствие архитектурной границы между данными и представлением.
9. Как отвечать на интервью по хукам
Формула сильного ответа:
- Короткое определение.
- Как работает внутри.
- Когда использовать.
- Типичная ошибка.
- Мини-пример.
Пример идеального ответа на вопрос:
В чем разница между
useMemoиuseCallback?
«useMemo мемоизирует результат вычисления, а useCallback мемоизирует функцию, то есть ссылку на колбэк. Я использую useMemo, когда есть тяжелые вычисления или когда важно стабилизировать объект/массив в props. useCallback применяю вместе с React.memo, когда передаю обработчик в дочерний компонент и хочу избежать лишних ререндеров. Частая ошибка — использовать их везде без профилирования, что усложняет код без пользы.»
10. Практический блок
Задача 1. Найти баг в useEffect
useEffect(() => {
fetchUser(userId).then(setUser);
}, []);
Что не так: userId отсутствует в dependencies, при смене userId данные не обновятся.
Задача 2. Оптимизировать список с useMemo
Дано: при каждом вводе в поле поиска происходит дорогая сортировка большого списка. Решение: вынести сортировку/фильтрацию в useMemo с корректными зависимостями.
Пример:
const preparedUsers = useMemo(() => {
return users
.filter((u) => u.name.toLowerCase().includes(query.toLowerCase()))
.sort((a, b) => a.score - b.score);
}, [users, query]);
Задача 3. Починить лишние ререндеры
Дано: React.memo не помогает. Проверить, не создаются ли новые функции и объекты в родителе на каждом рендере. Стабилизировать useCallback/useMemo там, где это обосновано.
Разбор на интервью обычно ждут такой:
- подтвердить лишние ререндеры в React DevTools Profiler;
- показать конкретный prop, который всегда новый по ссылке;
- исправить точечно, а не «обернуть все подряд».
Задача 4. Объяснить бесконечный цикл
useEffect(() => {
setFilters({ ...filters, page: 1 });
}, [filters]);
Каждый setFilters создает новый объект, filters меняется, эффект снова срабатывает. Нужно изменить логику триггера и condition, либо убрать effect.
Задача 5. Переписать код без лишних хуков
Если вычисление дешевое и не передается в мемоизированных детей, удалить useMemo/useCallback. Цель: код должен быть простым, а не «формально оптимизированным».
Типичный антипример:
const title = useMemo(() => `${firstName} ${lastName}`, [firstName, lastName]);
Если это простая строка без тяжелых вычислений и без влияния на дорогие дочерние компоненты, useMemo не нужен. На собеседовании такой рефакторинг часто оценивают как признак инженерной зрелости.
Мок-интервью по React Hooks
Получите разбор ответов по useEffect, useMemo, useCallback и поймите, где именно теряете баллы на интервью.
12. Заключение
React hooks — фундамент современной разработки и ключевой фильтр на интервью. Без понимания dependency array, ссылочной стабильности и механики рендера сложно пройти middle-уровень. Если хотите уверенно отвечать на react interview questions hooks, отрабатывайте не только определения, но и практические кейсы: бесконечные циклы, оптимизация react, react performance hooks, диагностика лишних ререндеров.
Лучший формат подготовки — короткий цикл: изучили тему -> решили задачу -> получили фидбек -> повторили. Чтобы ускорить прогресс, тренируйтесь на нашей платформе и фиксируйте ответы в формате реального собеседования.
Больше разборов в Telegram
Ежедневные вопросы по React, мини-задачи и разборы типичных ошибок на собеседованиях.
Автор
Lexicon Team
Читайте также
frontend
React и TypeScript: частые вопросы на интервью
Разбираем частые вопросы на интервью по React и TypeScript: типизация props, hooks, events, generics, refs, discriminated unions. А также типичные ошибки кандидатов и примеры сильных ответов.
frontend
React batching: как работает группировка обновлений
Разбираем batching в React на практике: очереди обновлений, автоматическая группировка в React 18+, flushSync, startTransition и production-ошибки.
frontend
React Strict Mode: зачем он нужен
Подробно разбираем React Strict Mode: какие проверки он включает, почему в dev все «вызывается дважды», какие баги ловит и как безопасно внедрять в production-командах.