Menu

Вопросы на собеседовании по React с ответами (React 19)

Около 30 вопросов по React для собеседования от начального до продвинутого уровня, у каждого короткий ответ, который примет интервьюер, а для тех, что проще увидеть, чем прочитать, живой код.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Это вопросы по React, которые чаще всего звучат на собеседованиях, сгруппированные по уровню и с ответами в том виде, в каком их ждёт интервьюер: коротко, верно для React 19 и с причиной, стоящей за правилом. Там, где увидеть поведение лучше, чем прочитать о нём, у ответа есть живой редактор, а несколько вопросов просят предсказать вывод консоли до запуска.

Прежде чем нажать: что выведет консоль под превью и что покажет кнопка? Ответ в разделе для начинающих ниже.

Вопросы для начинающих

Что такое React?

React это библиотека JavaScript для построения пользовательских интерфейсов из компонентов. Вы пишете функции, которые возвращают описание UI для текущих данных, а когда данные меняются, React выясняет, что изменилось, и обновляет DOM за вас. Он отвечает только за слой представления: маршрутизация, загрузка данных и формы берутся из других библиотек или из фреймворка вроде Next.js.

Что такое JSX?

JSX это расширение синтаксиса, которое позволяет писать разметку внутри JavaScript. Компилятор (Babel, esbuild, SWC) превращает каждый тег в вызов, создающий элемент React, поэтому <p className="note">{text}</p> становится объектом с type: 'p' и пропсами. Поскольку это JavaScript, используется className вместо class, выражения пишутся в {}, и каждый тег должен быть закрыт. Правила подробно разобраны на странице о JSX.

Чем компонент отличается от элемента?

Компонент это функция (или класс), которая принимает пропсы и возвращает UI. Элемент это обычный объект, который создаёт JSX и который описывает, что рендерить: <Avatar size={40} /> это элемент, у которого type равен функции Avatar. React вызывает ваш компонент, чтобы превратить элементы в новые элементы, вплоть до тегов DOM. Элементы дешёвые и неизменяемые; компонент вы никогда не вызываете сами.

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

Пропсы передаёт родитель, и внутри компонента они доступны только для чтения. Состоянием владеет компонент, оно меняется со временем через сеттер, и каждое изменение планирует новый рендер. Часто говорят так: пропсы настраивают компонент, состояние это его память. Одно и то же значение может быть состоянием в родителе и пропсом в его дочернем компоненте.

Что выводит первый пример на этой странице?

Он выводит logged: 0, а кнопка показывает Count: 1, а не 2. Состояние это снимок: count константа на весь рендер, поэтому оба вызова setCount(count + 1) вычисляют 0 + 1, а лог читает старое значение, потому что новое существует только в следующем рендере. Чтобы прибавить два, используйте форму с функцией обновления: setCount((c) => c + 1) дважды.

Зачем спискам ключи и почему индекс плохой ключ?

React использует key, чтобы сопоставить каждый элемент списка с элементом из предыдущего рендера. Если использовать индекс массива и вставить элемент в начало, каждый индекс сдвинется, поэтому React оставит состояние каждой строки на её старой позиции и привяжет его к неправильным данным.

Отметьте Ada, затем нажмите «Add to top»: отметка перепрыгнет на новую первую строку. Замените key={index} на key={person.id} и попробуйте снова, и отметка останется у Ada. В разработке React также предупреждает об отсутствующих ключах; превью работает как продакшен-сборка, поэтому здесь вы этого предупреждения не увидите.

Что такое контролируемое поле ввода?

Поле, значение которого берётся из состояния React: вы передаёте value и обновляете состояние в onChange, поэтому React становится единственным источником истины, и вы можете проверять или преобразовывать каждое нажатие клавиши. Неконтролируемое поле хранит своё значение в DOM, и вы читаете его, когда нужно, через ref или FormData формы. Контролируемые поля это выбор по умолчанию, когда UI реагирует на то, что печатает пользователь.

Что означает подъём состояния?

Когда двум компонентам нужны одни и те же данные, вы переносите состояние в их ближайшего общего родителя и передаёте его вниз пропсами вместе с функцией для его изменения. Дети перестают владеть собственными копиями, поэтому никогда не расходятся. Это стандартный ответ на вопрос «как соседним компонентам делить состояние», прежде чем браться за контекст или хранилище.

Каковы правила хуков?

Вызывайте хуки только на верхнем уровне компонента или пользовательского хука, никогда внутри условий, циклов или вложенных функций, и только из функций React, а не из обычных функций JavaScript. React распознаёт каждый хук по порядку вызовов, поэтому этот порядок должен быть одинаковым при каждом рендере. Оба правила проверяет пакет eslint-plugin-react-hooks.

Что такое фрагмент?

Фрагмент группирует несколько элементов, не добавляя узел DOM: <>...</> или <Fragment key={id}>, когда внутри списка нужен ключ. Компонент должен возвращать один корень, а фрагменты позволяют этому корню состоять из нескольких соседей, что важно внутри таблиц и flex-раскладок, где лишний div сломал бы разметку.

Вопросы среднего уровня

Как работает массив зависимостей useEffect?

React выполняет эффект после фиксации рендера (обычно после отрисовки браузером), а затем снова после каждого последующего рендера, в котором изменилось значение из массива (сравнение через Object.is). Без массива он выполняется после каждого рендера; с [] один раз после первого рендера. Каждое значение из компонента, которое читает эффект, должно быть в массиве, и линтер сообщает, когда какого-то не хватает.

Когда выполняется очистка эффекта?

Функция, которую вы возвращаете из эффекта, выполняется перед повторным запуском эффекта с новыми зависимостями и один раз при размонтировании компонента. Там вы отменяете то, что настроил эффект: очищаете таймер, удаляете обработчик, закрываете соединение.

Нажмите «Switch room» и смотрите в консоль: сначала выполняется очистка для старой комнаты, затем эффект подключается к новой. «Hide chat» выполняет только очистку. В разработке StrictMode при монтировании добавляет ещё одно подключение, отключение и подключение, чтобы проверить, что очистка работает; превью работает как продакшен-сборка, поэтому вы видите только настоящую последовательность.

Почему состояние внутри setInterval выглядит устаревшим?

Колбэк интервала это замыкание над count из рендера, который его создал. С пустым массивом зависимостей эффект больше никогда не выполняется, поэтому колбэк вечно читает тот первый count.

broken доходит до 1 и останавливается, потому что каждый тик вычисляет 0 + 1. fixed продолжает считать, потому что функция обновления получает последнее значение. Добавление broken в массив зависимостей тоже работает, но ценой очистки и пересоздания интервала каждую секунду.

Что такое пакетная обработка (batching)?

React группирует несколько обновлений состояния в один рендер. Начиная с React 18 это работает везде: в обработчиках событий, таймаутах, промисах и нативных обработчиках событий. Если DOM нужно обновить сразу после одного обновления, от этого позволяет отказаться flushSync из react-dom, но нужен он редко.

Клик выводит один render 1 1 1, а не три рендера. До React 18 три обновления состояния внутри таймаута означали три рендера.

Что это выведет? (порядок эффектов)

Прочитайте код и запишите четыре строки, которые выведет консоль, по порядку, прежде чем смотреть на неё.

Рендеринг идёт сверху вниз: render App, затем render Child. Эффекты выполняются снизу вверх, после того как всё дерево зафиксировано: effect Child, затем effect App. Эффект родителя может полагаться на то, что эффекты его детей уже выполнились.

Чем useMemo отличается от useCallback?

useMemo(() => compute(a, b), [a, b]) кэширует результат вычисления. useCallback(fn, [deps]) кэширует саму функцию, то есть это то же самое, что useMemo(() => fn, [deps]). Оба помогают, только когда что-то ниже сравнивает по ссылке: дочерний компонент с memo или массив зависимостей другого хука. Начиная с React Compiler 1.0 (октябрь 2025 года) шаг сборки может добавлять эту мемоизацию автоматически, но ручная мемоизация по-прежнему работает, и вы будете видеть её в большинстве проектов.

Что делает React.memo и почему ему часто нужен useCallback?

memo(Component) пропускает повторный рендер компонента, когда его пропсы такие же, как в прошлый раз, сравнивая их по одному через Object.is. Функция, написанная прямо в родителе, при каждом рендере новая, поэтому считается изменившимся пропсом и сводит memo на нет. useCallback сохраняет одну и ту же функцию между рендерами.

Очистите консоль и нажмите «Re-render parent»: появится только render inline handler. Другой дочерний компонент получил ту же функцию и был пропущен. Уберите memo(...) вокруг Button, и оба будут выводить сообщение при каждом клике, а это показывает, что один useCallback ничего не делает.

Что такое контекст и чего он стоит?

Контекст передаёт значение каждому компоненту ниже провайдера, не протягивая его через пропсы: createContext, затем <ThemeContext value={theme}> в React 19 (или <ThemeContext.Provider>) и useContext(ThemeContext), чтобы его прочитать. Цена в том, что каждый компонент, который читает контекст, перерендеривается при изменении значения, и memo этого не останавливает. Удешевить его можно, разделив несвязанные значения на отдельные контексты, обернув значение-объект в useMemo и не держа быстро меняющееся состояние в контексте на всё приложение. См. useContext.

Что такое рефы и когда использовать их вместо состояния?

useRef(initial) возвращает объект, свойство current которого сохраняется между рендерами, а его изменение не вызывает рендер. Используйте ref для значений, от которых не зависит экран (id таймера, предыдущее значение, флаг), и для доступа к DOM (<input ref={inputRef}>, затем inputRef.current.focus()). Если значение показывается на экране, ему место в состоянии.

Что такое виртуальный DOM и что такое согласование?

При каждом рендере React строит новое дерево элементов и сравнивает его с предыдущим; это сравнение и есть согласование (reconciliation). Если тип элемента изменился (div стал section или ProfileA стал ProfileB), React уничтожает это поддерево вместе с его состоянием и строит новое. Если тип тот же, он сохраняет узел DOM и обновляет изменившиеся пропсы, а для сопоставления детей в списках использует ключи. «Виртуальный DOM» это популярное название этого дерева в памяти; скорость берётся из того, что меняется только изменившееся, а не из быстроты самого дерева.

Что такое пользовательский хук?

Функция, имя которой начинается с use и которая вызывает другие хуки, чтобы переиспользовать логику с состоянием между компонентами: useOnlineStatus, useLocalStorage, useDebouncedValue. Каждый компонент, который её вызывает, получает собственное независимое состояние; пользовательский хук делится логикой, а не данными. См. пользовательские хуки.

Что делает StrictMode?

<StrictMode> включает дополнительные проверки только в разработке. Он рендерит компоненты дважды, чтобы выявить нечистый рендеринг, при монтировании один раз выполняет setup, cleanup и снова setup эффектов, чтобы выявить пропущенную очистку, делает то же для ref-колбэков и предупреждает об устаревших API. В продакшен-сборке ничего этого не происходит, поэтому двойной вывод в консоль в разработке ожидаем и не является ошибкой.

Вопросы продвинутого уровня

Что это выведет? (рендер, layout-эффект, эффект)

Ещё одно предсказание. Три лога, три разных момента рендера.

Порядок такой: рендер, затем useLayoutEffect, затем useEffect, хотя useEffect написан первым. Layout-эффекты выполняются после того, как React обновил DOM, но до отрисовки браузером, поэтому подходят для измерения элемента; обычные эффекты выполняются позже, как правило после отрисовки, и должны быть выбором по умолчанию.

Что такое граница ошибок?

Классовый компонент, который перехватывает ошибки, выброшенные при рендере его детей, и показывает запасной UI вместо размонтирования всего приложения. Ему нужен static getDerivedStateFromError (чтобы переключиться на запасной UI) и обычно componentDidCatch (чтобы залогировать). Эквивалента в виде хука по-прежнему нет, поэтому в проектах на функциональных компонентах команды пишут один класс или используют пакет react-error-boundary.

Нажмите «Break it»: запасной UI заменяет только детей границы, кнопка остаётся, а React всё равно сообщает о перехваченной ошибке в консоли. Границы не перехватывают ошибки в обработчиках событий, в асинхронном коде вроде setTimeout или в самой границе. Для них используют try/catch и устанавливают состояние.

Что такое портал?

createPortal(children, domNode) из react-dom рендерит детей в другой узел DOM, обычно document.body, сохраняя их на том же месте в дереве React. Модальные окна, подсказки и уведомления используют его, чтобы вырваться из overflow: hidden или контекста наложения родителя. Контекст через портал по-прежнему работает, а события всплывают по дереву React, поэтому клик внутри портала доходит до onClick родителя, хотя узлы DOM находятся в другом месте. См. порталы.

Что такое Suspense и lazy?

lazy(() => import('./Chart')) загружает код компонента при его первом рендере, что разделяет бандл. <Suspense fallback={<Spinner />}> показывает запасной вариант, пока что-то внутри ещё загружается: ленивый код или данные, прочитанные через use(promise) или фреймворк с поддержкой Suspense. Расстановка границ решает, какие части страницы ждут вместе.

import { lazy, Suspense } from 'react';

const Chart = lazy(() => import('./Chart.jsx'));

export default function Dashboard() {
    return (
        <Suspense fallback={<p>Loading chart...</p>}>
            <Chart />
        </Suspense>
    );
}

Что делает useTransition?

Он помечает обновление состояния как несрочное. const [isPending, startTransition] = useTransition() и startTransition(() => setTab('posts')) позволяют React сохранять отзывчивость текущего экрана и прерывать медленный рендер, если приходит более срочное обновление, например нажатие клавиши. isPending подсказывает, что нужно показать индикатор. В React 19 передаваемая функция может быть асинхронной, и React называет это действием (action). Не используйте его для обновления собственного значения текстового поля; это обновление должно быть срочным.

Что такое React Server Components?

Компоненты, которые выполняются только на сервере (или во время сборки) и отправляют в браузер свой отрендеренный результат, но никогда не свой код. Они могут быть async и напрямую читать базу данных или файловую систему, но не могут использовать состояние, эффекты или API браузера. Файл с пометкой 'use client' начинает клиентскую границу, где живут интерактивные компоненты, а 'use server' помечает серверные функции, которые может вызывать клиентский код. Их используют через фреймворк, например Next.js с App Router; у обычных приложений на Vite нет сервера, чтобы их выполнять.

Что изменилось в React 19?

  • Actions: асинхронные функции в переходах, состояние ожидания которых обрабатывается за вас. <form action={fn}> вызывает fn с FormData формы.
  • Новые хуки: useActionState для результата и состояния ожидания формы, useOptimistic для оптимистичного UI и useFormStatus в react-dom.
  • use: читает промис (приостанавливаясь, пока он не выполнится) или контекст, и в отличие от других хуков его можно вызывать внутри условия.
  • ref как пропс: функциональные компоненты получают ref как любой другой пропс, поэтому новому коду forwardRef больше не нужен. Ref-колбэки могут возвращать функцию очистки.
  • <Context> как провайдер, а также теги <title> и <meta>, которые поднимаются в head документа.
  • Удалено: ReactDOM.render, строковые рефы, устаревший контекст и defaultProps у функциональных компонентов (используйте параметры по умолчанию).
// React 19: ref is a normal prop
function TextInput({ ref, ...props }) {
    return <input ref={ref} {...props} />;
}

На странице о React 19 есть запускаемые примеры каждого пункта.

Как тестировать компоненты React?

С React Testing Library и раннером вроде Vitest или Jest. Вы рендерите компонент, находите элементы так, как это сделал бы пользователь (по роли, подписи или тексту), взаимодействуете через user-event и проверяете то, что видно, а не внутреннее состояние. Так тесты остаются валидными при рефакторинге.

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';

test('counts clicks', async () => {
    const user = userEvent.setup();
    render(<Counter />);
    await user.click(screen.getByRole('button', { name: /clicked 0 times/i }));
    expect(screen.getByRole('button')).toHaveTextContent('Clicked 1 times');
});

toHaveTextContent берётся из @testing-library/jest-dom. Для целых сценариев по нескольким страницам команды добавляют сверху Playwright или Cypress.

Как найти и исправить проблему с производительностью?

Сначала измерьте. Откройте вкладку Profiler в React DevTools, запишите медленное взаимодействие и прочитайте flame-диаграмму: она показывает, какие компоненты рендерились, сколько времени занял каждый и (с настройкой «record why each component rendered») почему. Типичные решения в том порядке, в каком их стоит пробовать: перенести состояние ниже, чтобы от него зависело меньше компонентов, передавать медленные поддеревья как children, мемоизировать дорогую часть через memo, useMemo и useCallback, виртуализировать длинные списки и откладывать тяжёлые обновления через useTransition или useDeferredValue. Профилируйте в продакшен- или профилирующей сборке; сборки для разработки медленнее и преувеличивают время рендера.

Почему компонент должен быть чистым?

При одних и тех же пропсах, состоянии и контексте компонент должен возвращать один и тот же JSX и ничего не менять вне себя во время рендера. React может рендерить компонент больше одного раза, не по порядку или выбросить рендер (переходы, Suspense, StrictMode), поэтому побочные эффекты в теле, например изменение переменной или отправка запроса, выполняются непредсказуемое число раз. Побочным эффектам место прежде всего в обработчиках событий, а в эффектах, когда их не вызвало никакое событие.

Когда не стоит использовать эффект?

Когда значение можно вычислить во время рендера (отфильтрованные списки, полные имена, итоги), когда работу вызывает действие пользователя (поместите её в обработчик события) и когда нужно сбросить состояние при изменении пропса (используйте key). Эффекты нужны для синхронизации с чем-то вне React: сетевым соединением, API браузера, сторонним виджетом. Цепочки эффектов, которые устанавливают состояние, частый источник лишних рендеров и ошибок.

Часто задаваемые вопросы

Что такое React?

React это библиотека JavaScript для построения пользовательских интерфейсов из компонентов. Вы описываете, как должен выглядеть экран для текущих данных, а React обновляет настоящий DOM, когда эти данные меняются.

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

Пропсы это входные данные, которые родитель передаёт компоненту, и компонент считает их доступными только для чтения. Состояние это данные, которыми владеет компонент и которые со временем меняются через сеттер, из-за чего React рендерит его снова.

Что такое хуки в React?

Хуки это функции с именами, начинающимися на use, которые позволяют функциональному компоненту использовать возможности React: useState для состояния, useEffect для побочных эффектов, useContext для контекста, useRef для значений, которые не вызывают рендер, и другие.

Что такое виртуальный DOM?

Так называют описание UI в памяти React. При каждом рендере React строит новое описание, сравнивает его с предыдущим и применяет к настоящему DOM только различия. Это сравнение называется согласованием (reconciliation).

Почему в списках React важны ключи?

Ключ говорит React, какой элемент списка какой между рендерами. Со стабильными ключами (например id) React сохраняет состояние и узел DOM каждого элемента привязанными к правильным данным при добавлении, удалении или перестановке элементов. Индекс массива ломает это, когда меняется порядок.

Что такое JSX?

JSX это расширение синтаксиса, которое позволяет писать похожую на HTML разметку в JavaScript. Компилятор превращает каждый тег в вызов функции, создающей элемент React, поэтому <h1>Hi</h1> становится обычным объектом, описывающим h1.

Иллюстрация языков программирования Coddy

Учитесь программировать с Coddy

НАЧАТЬ