Перейти к содержанию
Фронтенд

14 вопросов по теме «Frontend: React» на собеседовании

В этом материале — 14 вопросов из русской колоды RecallDeck по теме «Frontend: React». Сначала сформулируйте короткий ответ сами, затем откройте подробный разбор и проверьте примеры, ограничения и отказные случаи.

11 мин чтения14 подробных ответовПроверено 24 августа 2026
Главная мысль

Связывайте ответ с браузером, доступностью, производительностью и пользовательским состоянием — именно там видна зрелость frontend-инженера.

Вопросы и ответы

14 подробных ответов

01

Что такое JSX и во что он компилируется?

Короткий ответ: JSX — это синтаксический сахар над вызовами функции создания элементов. Компилятор (Babel/SWC) превращает его в вызовы jsx-runtime (или React.createElement в старом transform), которые возвращают обычные JS-объекты — описания UI, а не реальные DOM-узлы.

Подробно:

  1. Это выражение, а не HTML — JSX компилируется в JavaScript и встраивается везде, где допустимо выражение.
  2. Новый JSX-transform (React 17+) — импортирует jsx/jsxs из react/jsx-runtime, поэтому import React ради JSX больше не нужен.
  3. Возвращает объект-элемент — вида { type, key, props }; реальный DOM появляется только при рендере.
  4. Регистр имеет значение — тег с заглавной буквы — компонент, со строчной — 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

Что такое компоненты и пропсы и чем пропсы отличаются от состояния?

Короткий ответ: Компонент — это функция, которая принимает объект пропсов и возвращает React-элементы. Пропсы — входные данные, передаваемые сверху вниз и доступные только для чтения; состояние (state) — внутренние изменяемые данные компонента. Данные текут в одну сторону: от родителя к детям.

Подробно:

  • Однонаправленный поток — родитель передаёт пропсы вниз; чтобы изменить данные у родителя, ребёнок вызывает переданный колбэк.
  • Пропсы иммутабельны — компонент не должен их мутировать; это контракт «входных параметров».
  • Состояние локально — меняется только через сеттер, и каждое изменение планирует ре-рендер.
  • Lifting state up — общее состояние двух детей поднимают в ближайшего общего родителя.
Аспект Пропсы Состояние
Источник от родителя внутри компонента
Изменяемость read-only через сеттер
Что триггерит ре-рендер смена у родителя вызов сеттера

⚠️ Частая ошибка: мутировать пропсы или менять состояние напрямую (state.x = 1) вместо вызова сеттера — React не увидит изменения.

03

Что такое виртуальный DOM и реконсиляция и почему важны ключи (keys)?

Короткий ответ: Виртуальный DOM — это лёгкое дерево React-элементов в памяти. При ре-рендере React строит новое дерево, сравнивает (diff) его со старым и применяет к реальному DOM только различия. Ключи (keys) дают элементам списка стабильную идентичность между рендерами.

Подробно:

  1. Diff по типу и позиции — если тип узла совпал, React обновляет пропсы; если изменился — пересоздаёт всё поддерево.
  2. Списки сопоставляются по key — без ключа React сопоставляет по индексу позиции.
  3. key должен быть стабильным и уникальным — обычно id из данных, не индекс.
  4. 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, что такое автоматическое батчинг и зачем нужны функциональные обновления?

Короткий ответ: useState возвращает текущее значение и сеттер; вызов сеттера планирует ре-рендер с новым значением. React 18+ автоматически батчит несколько обновлений в один ре-рендер — даже внутри промисов и таймеров. Функциональная форма setX(prev => …) нужна, когда новое значение зависит от предыдущего.

Подробно:

  1. Сеттер не меняет переменную сразу — значение в текущем рендере «заморожено» (snapshot); новое появится в следующем рендере.
  2. Автоматическое батчинг — несколько setState подряд = один ре-рендер; в React 18 это работает и в async-коллбэках.
  3. Функциональное обновление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: массив зависимостей, очистка и когда он запускается?

Короткий ответ: useEffect синхронизирует компонент с внешней системой (подписки, таймеры, сеть) и запускается после коммита в DOM. Массив зависимостей определяет, когда эффект перезапускается; функция очистки выполняется перед следующим запуском и при размонтировании.

Подробно:

  1. Когда запускается — после рендера и отрисовки; [] — один раз при монтировании, [a, b] — при изменении a или b, без массива — после каждого рендера.
  2. Очистка — возвращаемая функция отписывает/убирает таймеры, предотвращая утечки и гонки.
  3. Все используемые значения — в зависимостях — иначе устаревшие замыкания.
useEffect(() => {
  const id = setInterval(() => tick(), 1000);
  return () => clearInterval(id); // очистка перед следующим запуском / размонтированием
}, [tick]);

⚠️ Частая ошибка: класть в эффект логику, которая должна быть обработчиком события (реакция на клик ≠ синхронизация), и «обманывать» линтер, убирая зависимости вместо устранения причины.

06

В чём разница между useMemo, useCallback и React.memo и когда они реально помогают?

Короткий ответ: useMemo кэширует результат вычисления, useCallback кэширует ссылку на функцию, а React.memo пропускает ре-рендер компонента, если пропсы не изменились (по поверхностному сравнению). Все трое — про стабильность ссылок и пропуск лишней работы, но помогают только при реальной дороговизне.

Подробно:

  • Работают вместеmemo сравнивает пропсы по ссылке, поэтому функции/объекты в пропсах надо стабилизировать через useCallback/useMemo.
  • Цена не нулевая — мемоизация сама занимает память и время на сравнение зависимостей.
Инструмент Что кэширует Когда помогает
useMemo значение вычисления дорогой расчёт; стабильная ссылка на объект
useCallback ссылку на функцию функция уходит в проп memo-компонента или в deps
React.memo результат рендера компонент часто получает те же пропсы

⚠️ Частая ошибка: оборачивать всё подряд «на всякий случай» — преждевременная оптимизация добавляет сложность и иногда замедляет.

07

Зачем нужен useRef, чем ref отличается от состояния и как работает ref-as-prop в React 19?

Короткий ответ: useRef хранит изменяемое значение в .current, которое переживает ре-рендеры и не вызывает их при изменении. Главное применение — императивный доступ к DOM-узлу. В React 19 ref можно передавать как обычный проп, и forwardRef больше не нужен.

Подробно:

  1. ref vs state — менять ref.current не вызывает ре-рендер; используйте состояние, если значение влияет на разметку.
  2. DOM-доступ<input ref={inputRef} />, затем inputRef.current.focus().
  3. 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)?

Короткий ответ: Кастомный хук — это функция с префиксом use, которая вызывает другие хуки, чтобы переиспользовать логику с состоянием. Правила хуков: вызывать их только на верхнем уровне компонента/хука и только из React-функций — не в условиях, циклах или вложенных функциях.

Подробно:

  1. Переиспользуется логика, не состояние — два компонента с одним хуком получают независимые экземпляры состояния.
  2. Почему нельзя условно — React сопоставляет хуки по порядку вызова; ветвление сдвигает порядок и ломает соответствие.
  3. Конвенция 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 и как решить проблему лишних ре-рендеров?

Короткий ответ: Context передаёт данные сквозь дерево без prop drilling — удобен для редко меняющихся «глобальных» значений (тема, локаль, текущий пользователь). Проблема: при смене value провайдера ре-рендерятся все потребители. Митигация — разбить контекст, мемоизировать value и держать в нём минимум.

Подробно:

  1. Не замена стейт-менеджера — для часто меняющихся данных контекст вызывает лавину ре-рендеров.
  2. Разделяй контексты — отдельный контекст для значения и для функций-диспатчеров, чтобы потребители подписывались на нужное.
  3. Мемоизируй value — иначе новый объект на каждый рендер провайдера бьёт по всем потребителям.
const value = useMemo(() => ({ user, logout }), [user, logout]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;

⚠️ Частая ошибка: передавать в value свежесозданный объект { user, logout } инлайном — он новый на каждый рендер, и все потребители ре-рендерятся впустую.

10

Какие есть подходы к управлению состоянием и чем серверное состояние отличается от клиентского?

Короткий ответ: Состояние стоит классифицировать по владельцу и времени жизни: локальное состояние компонента, поднятое/контекст для общего 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'?

Короткий ответ: Server Components выполняются только на сервере, не попадают в JS-бандл клиента и могут напрямую обращаться к данным (БД, файлы, секреты). Client Components — это привычные интерактивные компоненты с состоянием и эффектами. Директива 'use client' помечает границу, ниже которой компоненты исполняются на клиенте.

Подробно:

  1. RSC по умолчанию (во фреймворках с RSC) — рендерятся на сервере, отдают сериализованный результат; меньше JS на клиенте.
  2. Нет хуков и обработчиков — в RSC нельзя useState/useEffect/onClick.
  3. 'use client' — помечает модуль и его импорт-границу как клиентскую; серверные компоненты можно передавать внутрь как children.
[Server Component]  ← fetch из БД, без JS на клиенте
   | children
   v
[ 'use client' ]    ← граница
   |
[Client Component]  ← useState, onClick, гидратация

⚠️ Частая ошибка: ставить 'use client' в самый верх дерева — тогда почти всё уезжает на клиент, и преимущество RSC (меньше бандл, доступ к данным) теряется.

12

Как работают Suspense и Error Boundaries и что они перехватывают?

Короткий ответ: Suspense показывает fallback, пока дочерний компонент «приостановлен» (ждёт загрузки данных или кода), и переключается на контент, когда тот готов. Error Boundary ловит ошибки рендера в поддереве и показывает запасной UI. Они дополняют друг друга: Suspense — для ожидания, boundary — для падений.

Подробно:

  1. Suspense ловит ожидание — работает с lazy(), RSC и библиотеками, бросающими промис / использующими use.
  2. Error Boundary ловит ошибки — пока только классовый компонент с getDerivedStateFromError/componentDidCatch.
  3. Что НЕ ловит boundary — ошибки в обработчиках событий, async-коде и в самом fallback.
<ErrorBoundary fallback={<Error />}>
  <Suspense fallback={<Spinner />}>
    <Profile />   {/* приостановится, пока грузятся данные */}
  </Suspense>
</ErrorBoundary>

⚠️ Частая ошибка: ждать, что Error Boundary поймает ошибку из onClick или из промиса — там нужен обычный try/catch.

13

Чем контролируемые поля отличаются от неконтролируемых и как работают form Actions в React 19?

Короткий ответ: В контролируемом поле значение хранится в state и задаётся через value + onChange; в неконтролируемом — живёт в самом DOM и читается через ref. React 19 добавил form Actions: <form action={fn}> плюс хуки useActionState, useFormStatus и useOptimistic для статуса отправки и оптимистичных обновлений.

Подробно:

  1. Контролируемое — единый источник правды в React, удобно для валидации на лету.
  2. Неконтролируемое — проще, меньше ре-рендеров; значение берут из FormData.
  3. ActionsuseActionState хранит результат/ошибку и 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 и что такое гидратация?

Короткий ответ: Это разные моменты генерации 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, архитектуру и поведенческие истории.

Начать подготовку

Продолжить подготовку

Библиотека собеседований RecallDeck

Подробные русские ответы, разборы этапов найма и планы подготовки для российского IT-рынка.

RSS