Связывайте ответ с браузером, доступностью, производительностью и пользовательским состоянием — именно там видна зрелость frontend-инженера.
Вопросы и ответы
14 подробных ответов
01Что такое JSX и во что он компилируется?
junior
Короткий ответ: JSX — это синтаксический сахар над вызовами функции создания элементов. Компилятор (Babel/SWC) превращает его в вызовы jsx-runtime (или React.createElement в старом transform), которые возвращают обычные JS-объекты — описания UI, а не реальные DOM-узлы.
Подробно:
- Это выражение, а не HTML — JSX компилируется в JavaScript и встраивается везде, где допустимо выражение.
- Новый JSX-transform (React 17+) — импортирует
jsx/jsxsизreact/jsx-runtime, поэтомуimport Reactради JSX больше не нужен. - Возвращает объект-элемент — вида
{ type, key, props }; реальный DOM появляется только при рендере. - Регистр имеет значение — тег с заглавной буквы — компонент, со строчной — DOM-элемент.
const el = <button className="btn" onClick={fn}>Save</button>;
// новый transform компилирует это в:
import { jsx } from "react/jsx-runtime";
const el = jsx("button", { className: "btn", onClick: fn, children: "Save" });
⚠️ Частая ошибка: считать JSX строкой или HTML. На деле это JS: пишут className, а не class, а атрибуты — в camelCase (onClick, tabIndex).
02Что такое компоненты и пропсы и чем пропсы отличаются от состояния?
junior
Короткий ответ: Компонент — это функция, которая принимает объект пропсов и возвращает React-элементы. Пропсы — входные данные, передаваемые сверху вниз и доступные только для чтения; состояние (state) — внутренние изменяемые данные компонента. Данные текут в одну сторону: от родителя к детям.
Подробно:
- Однонаправленный поток — родитель передаёт пропсы вниз; чтобы изменить данные у родителя, ребёнок вызывает переданный колбэк.
- Пропсы иммутабельны — компонент не должен их мутировать; это контракт «входных параметров».
- Состояние локально — меняется только через сеттер, и каждое изменение планирует ре-рендер.
- Lifting state up — общее состояние двух детей поднимают в ближайшего общего родителя.
| Аспект | Пропсы | Состояние |
|---|---|---|
| Источник | от родителя | внутри компонента |
| Изменяемость | read-only | через сеттер |
| Что триггерит ре-рендер | смена у родителя | вызов сеттера |
⚠️ Частая ошибка: мутировать пропсы или менять состояние напрямую (state.x = 1) вместо вызова сеттера — React не увидит изменения.
03Что такое виртуальный DOM и реконсиляция и почему важны ключи (keys)?
middle
Короткий ответ: Виртуальный DOM — это лёгкое дерево React-элементов в памяти. При ре-рендере React строит новое дерево, сравнивает (diff) его со старым и применяет к реальному DOM только различия. Ключи (keys) дают элементам списка стабильную идентичность между рендерами.
Подробно:
- Diff по типу и позиции — если тип узла совпал, React обновляет пропсы; если изменился — пересоздаёт всё поддерево.
- Списки сопоставляются по
key— без ключа React сопоставляет по индексу позиции. keyдолжен быть стабильным и уникальным — обычно id из данных, не индекс.- Index-as-key ломается при вставке/удалении/сортировке: состояние и DOM «прилипают» к позиции, а не к элементу.
Было: [A(key=a)] [B(key=b)] [C(key=c)]
Вставили X в начало по id-ключам:
Стало: [X(key=x)] [A(key=a)] [B(key=b)] [C(key=c)] ← A,B,C переиспользованы
С index-as-key вставка X сдвигает ключи 0,1,2,3 →
React думает, что изменился каждый элемент → правит не те узлы.
⚠️ Частая ошибка: key={index} в изменяемом списке — приводит к потере фокуса, неверному состоянию полей и лишним перерисовкам.
04Как работает useState, что такое автоматическое батчинг и зачем нужны функциональные обновления?
middle
Короткий ответ: useState возвращает текущее значение и сеттер; вызов сеттера планирует ре-рендер с новым значением. React 18+ автоматически батчит несколько обновлений в один ре-рендер — даже внутри промисов и таймеров. Функциональная форма setX(prev => …) нужна, когда новое значение зависит от предыдущего.
Подробно:
- Сеттер не меняет переменную сразу — значение в текущем рендере «заморожено» (snapshot); новое появится в следующем рендере.
- Автоматическое батчинг — несколько
setStateподряд = один ре-рендер; в React 18 это работает и в async-коллбэках. - Функциональное обновление —
setCount(c => c + 1)читает актуальное значение, обходя устаревшее замыкание.
// БАГ: все три читают одно значение из снапшота
setCount(count + 1); setCount(count + 1); setCount(count + 1); // +1
// Верно: каждое обновление получает свежий prev
setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); // +3
⚠️ Частая ошибка: обновлять состояние по его прежнему значению через count + 1 в цикле/обработчике — устаревшее замыкание даёт неверный результат.
05Как работает useEffect: массив зависимостей, очистка и когда он запускается?
middle
Короткий ответ: useEffect синхронизирует компонент с внешней системой (подписки, таймеры, сеть) и запускается после коммита в DOM. Массив зависимостей определяет, когда эффект перезапускается; функция очистки выполняется перед следующим запуском и при размонтировании.
Подробно:
- Когда запускается — после рендера и отрисовки;
[]— один раз при монтировании,[a, b]— при измененииaилиb, без массива — после каждого рендера. - Очистка — возвращаемая функция отписывает/убирает таймеры, предотвращая утечки и гонки.
- Все используемые значения — в зависимостях — иначе устаревшие замыкания.
useEffect(() => {
const id = setInterval(() => tick(), 1000);
return () => clearInterval(id); // очистка перед следующим запуском / размонтированием
}, [tick]);
⚠️ Частая ошибка: класть в эффект логику, которая должна быть обработчиком события (реакция на клик ≠ синхронизация), и «обманывать» линтер, убирая зависимости вместо устранения причины.
06В чём разница между useMemo, useCallback и React.memo и когда они реально помогают?
middle
Короткий ответ: useMemo кэширует результат вычисления, useCallback кэширует ссылку на функцию, а React.memo пропускает ре-рендер компонента, если пропсы не изменились (по поверхностному сравнению). Все трое — про стабильность ссылок и пропуск лишней работы, но помогают только при реальной дороговизне.
Подробно:
- Работают вместе —
memoсравнивает пропсы по ссылке, поэтому функции/объекты в пропсах надо стабилизировать черезuseCallback/useMemo. - Цена не нулевая — мемоизация сама занимает память и время на сравнение зависимостей.
| Инструмент | Что кэширует | Когда помогает |
|---|---|---|
| useMemo | значение вычисления | дорогой расчёт; стабильная ссылка на объект |
| useCallback | ссылку на функцию | функция уходит в проп memo-компонента или в deps |
| React.memo | результат рендера | компонент часто получает те же пропсы |
⚠️ Частая ошибка: оборачивать всё подряд «на всякий случай» — преждевременная оптимизация добавляет сложность и иногда замедляет.
07Зачем нужен useRef, чем ref отличается от состояния и как работает ref-as-prop в React 19?
senior
Короткий ответ: useRef хранит изменяемое значение в .current, которое переживает ре-рендеры и не вызывает их при изменении. Главное применение — императивный доступ к DOM-узлу. В React 19 ref можно передавать как обычный проп, и forwardRef больше не нужен.
Подробно:
- ref vs state — менять
ref.currentне вызывает ре-рендер; используйте состояние, если значение влияет на разметку. - DOM-доступ —
<input ref={inputRef} />, затемinputRef.current.focus(). - React 19: ref-as-prop — функциональный компонент принимает
refпрямо в пропсах.
function TextInput({ ref, ...props }: {
ref?: React.Ref<HTMLInputElement>;
} & React.ComponentProps<"input">) {
return <input ref={ref} {...props} />;
}
// forwardRef больше не требуется; <TextInput ref={myRef} />
⚠️ Частая ошибка: хранить в ref данные, которые должны быть в state, — UI не обновится, потому что запись в .current не триггерит рендер.
08Что такое кастомные хуки и каковы правила хуков (rules of hooks)?
senior
Короткий ответ: Кастомный хук — это функция с префиксом use, которая вызывает другие хуки, чтобы переиспользовать логику с состоянием. Правила хуков: вызывать их только на верхнем уровне компонента/хука и только из React-функций — не в условиях, циклах или вложенных функциях.
Подробно:
- Переиспользуется логика, не состояние — два компонента с одним хуком получают независимые экземпляры состояния.
- Почему нельзя условно — React сопоставляет хуки по порядку вызова; ветвление сдвигает порядок и ломает соответствие.
- Конвенция
use*— нужна линтеру и React, чтобы применять правила.
function useOnlineStatus() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
const on = () => setOnline(true), off = () => setOnline(false);
window.addEventListener("online", on);
window.addEventListener("offline", off);
return () => {
window.removeEventListener("online", on);
window.removeEventListener("offline", off);
};
}, []);
return online;
}
⚠️ Частая ошибка: вызвать хук внутри if или после раннего return — порядок хуков «съезжает» между рендерами и React падает.
09Когда использовать Context API и как решить проблему лишних ре-рендеров?
senior
Короткий ответ: Context передаёт данные сквозь дерево без prop drilling — удобен для редко меняющихся «глобальных» значений (тема, локаль, текущий пользователь). Проблема: при смене value провайдера ре-рендерятся все потребители. Митигация — разбить контекст, мемоизировать value и держать в нём минимум.
Подробно:
- Не замена стейт-менеджера — для часто меняющихся данных контекст вызывает лавину ре-рендеров.
- Разделяй контексты — отдельный контекст для значения и для функций-диспатчеров, чтобы потребители подписывались на нужное.
- Мемоизируй value — иначе новый объект на каждый рендер провайдера бьёт по всем потребителям.
const value = useMemo(() => ({ user, logout }), [user, logout]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
⚠️ Частая ошибка: передавать в value свежесозданный объект { user, logout } инлайном — он новый на каждый рендер, и все потребители ре-рендерятся впустую.
10Какие есть подходы к управлению состоянием и чем серверное состояние отличается от клиентского?
senior
Короткий ответ: Состояние стоит классифицировать по владельцу и времени жизни: локальное состояние компонента, поднятое/контекст для общего UI-состояния, внешний стор (Redux/Zustand) для сложного клиентского состояния и отдельный слой серверного состояния (React Query/SWR) для данных с сервера — с кэшем, инвалидизацией и фоновым обновлением.
Подробно:
- Серверное ≠ клиентское — серверные данные асинхронны, кэшируемы и устаревают; их не стоит дублировать в Redux вручную.
- Выбирай минимально достаточное — начни с
useState, поднимай выше только при необходимости.
| Тип | Инструмент | Для чего |
|---|---|---|
| Локальное | useState/useReducer |
состояние одного компонента |
| Общее UI | lifting / Context | тема, модалки, текущий таб |
| Клиентский стор | Redux Toolkit, Zustand | сложное глобальное состояние |
| Серверное | React Query, SWR | данные API: кэш, инвалидизация |
⚠️ Частая ошибка: хранить серверные данные в Redux вручную — приходится самому писать кэш, статусы загрузки и инвалидизацию, которые уже даёт React Query.
11Чем Server Components отличаются от Client Components в React 19 и где проходит граница 'use client'?
senior
Короткий ответ: Server Components выполняются только на сервере, не попадают в JS-бандл клиента и могут напрямую обращаться к данным (БД, файлы, секреты). Client Components — это привычные интерактивные компоненты с состоянием и эффектами. Директива 'use client' помечает границу, ниже которой компоненты исполняются на клиенте.
Подробно:
- RSC по умолчанию (во фреймворках с RSC) — рендерятся на сервере, отдают сериализованный результат; меньше JS на клиенте.
- Нет хуков и обработчиков — в RSC нельзя
useState/useEffect/onClick. 'use client'— помечает модуль и его импорт-границу как клиентскую; серверные компоненты можно передавать внутрь какchildren.
[Server Component] ← fetch из БД, без JS на клиенте
| children
v
[ 'use client' ] ← граница
|
[Client Component] ← useState, onClick, гидратация
⚠️ Частая ошибка: ставить 'use client' в самый верх дерева — тогда почти всё уезжает на клиент, и преимущество RSC (меньше бандл, доступ к данным) теряется.
12Как работают Suspense и Error Boundaries и что они перехватывают?
middle
Короткий ответ: Suspense показывает fallback, пока дочерний компонент «приостановлен» (ждёт загрузки данных или кода), и переключается на контент, когда тот готов. Error Boundary ловит ошибки рендера в поддереве и показывает запасной UI. Они дополняют друг друга: Suspense — для ожидания, boundary — для падений.
Подробно:
- Suspense ловит ожидание — работает с
lazy(), RSC и библиотеками, бросающими промис / использующимиuse. - Error Boundary ловит ошибки — пока только классовый компонент с
getDerivedStateFromError/componentDidCatch. - Что НЕ ловит boundary — ошибки в обработчиках событий, async-коде и в самом fallback.
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Spinner />}>
<Profile /> {/* приостановится, пока грузятся данные */}
</Suspense>
</ErrorBoundary>
⚠️ Частая ошибка: ждать, что Error Boundary поймает ошибку из onClick или из промиса — там нужен обычный try/catch.
13Чем контролируемые поля отличаются от неконтролируемых и как работают form Actions в React 19?
senior
Короткий ответ: В контролируемом поле значение хранится в state и задаётся через value + onChange; в неконтролируемом — живёт в самом DOM и читается через ref. React 19 добавил form Actions: <form action={fn}> плюс хуки useActionState, useFormStatus и useOptimistic для статуса отправки и оптимистичных обновлений.
Подробно:
- Контролируемое — единый источник правды в React, удобно для валидации на лету.
- Неконтролируемое — проще, меньше ре-рендеров; значение берут из
FormData. - Actions —
useActionStateхранит результат/ошибку иisPending;useFormStatusдаёт статус ближайшей формы;useOptimisticпоказывает оптимистичный результат до ответа.
function Comments() {
const [state, submit, pending] = useActionState(addComment, null);
return (
<form action={submit}>
<input name="text" />
<button disabled={pending}>Send</button>
{state?.error && <p>{state.error}</p>}
</form>
);
}
⚠️ Частая ошибка: держать поле «полуконтролируемым» — задать value без onChange; React сделает его readonly и предупредит в консоли.
14Чем отличаются CSR, SSR, SSG и потоковый SSR и что такое гидратация?
senior
Короткий ответ: Это разные моменты генерации HTML: CSR — в браузере, SSR — на сервере на каждый запрос, SSG — на этапе сборки, потоковый SSR — сервер отдаёт HTML кусками по мере готовности. Гидратация — это «оживление» серверного HTML на клиенте: React навешивает обработчики и связывает разметку со своим деревом.
Подробно:
- Гидратация требует совпадения — серверный и первый клиентский рендер должны дать одинаковый HTML.
- Streaming + Suspense — позволяет слать готовые части страницы, не дожидаясь медленных данных.
| Стратегия | Когда генерится HTML | Плюс |
|---|---|---|
| CSR | в браузере | простой хостинг, богатый клиент |
| SSR | на сервере на каждый запрос | свежие данные, SEO |
| SSG | при сборке | максимально быстро, кэш на CDN |
| Streaming | потоком по мере готовности | быстрый TTFB, прогрессивный показ |
⚠️ Частая ошибка: рендерить на сервере и клиенте разное (Date.now(), window, случайные id) — возникает hydration mismatch, и React предупреждает / перерисовывает.
Источники
Источники и редакционная политика
Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.
От чтения к воспроизведению
Отрепетируйте полный цикл интервью.
RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.