React и TypeScript: частые вопросы на интервью

Разбираем частые вопросы на интервью по React и TypeScript: типизация props, hooks, events, generics, refs, discriminated unions. А также типичные ошибки кандидатов и примеры сильных ответов.

20 марта 2026 г.18 минLexicon Team

Введение

Связка React и TypeScript на интервью почти никогда не сводится к вопросу "как написать тип для пропсов". Интервьюер обычно проверяет другое: умеете ли вы строить контракт компонента так, чтобы он оставался устойчивым при росте фичи, сохранял автодополнение и не скрывал баги под any.

Поэтому хорошие вопросы звучат чуть жестче, чем в учебнике. Почему React.FC не всегда лучший выбор? Когда interface удобнее type, а когда наоборот? Как типизировать generic-компонент, чтобы он не потерял вывод типов? Что делать с ответом API: доверять форме данных или сначала сузить тип? Эти вопросы перекликаются с темой React hooks на собеседовании.

Ниже разберем частые вопросы в том виде, в котором они полезны на техническом интервью: с кодом, ограничениями среды, типичными ошибками и формулировками сильных ответов.

Больше вопросов в Telegram

Ежедневные разборы и реальные кейсы с интервью.

Подписаться

Какие темы по React и TypeScript спрашивают чаще всего

Если отбросить экзотические вопросы, то ядро интервью обычно вращается вокруг семи блоков:

  1. Типизация props, children и событий.
  2. Типизация useState, useReducer, useRef и кастомных hooks.
  3. interface против type.
  4. Generic-компоненты и generic-hooks.
  5. unknown, any, type guards и сужение типов.
  6. Discriminated unions для состояний UI.
  7. Типизация интеграции с API, формами и ref-forwarding.

Слабый ответ на такие вопросы обычно выглядит одинаково: кандидат помнит синтаксис, но не может объяснить, чем это решение обернётся на практике. Например, говорит "я всегда использую React.FC", но не проговаривает, что код generic-компоненты и явный контроль над children с ним часто становятся менее читаемыми. Или отвечает "беру any, чтобы не мешал TypeScript", хотя на production-проекте это быстро убивает доверие к типовой системе.

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

Вопрос 1. Как типизировать props компонента

Самый частый базовый вопрос звучит просто, но на нем легко увидеть зрелость кандидата. Интервьюеру редко нужен сам факт, что вы умеете написать type Props = { ... }. Ему важнее, понимаете ли вы, какой API у компонента безопасен, а какой позволяет невозможные состояния.

Плохой контракт props

Проблема начинается, когда один компонент пытается обеспечить слишком много режимов работы без явного разделения:

type ButtonProps = {
  href?: string;
  onClick?: () => void;
  isLoading?: boolean;
  icon?: React.ReactNode;
  children?: React.ReactNode;
};

На уровне JSX такой контракт кажется удобным, но он допускает странные комбинации: href и onClick одновременно, isLoading без блокировки клика, отсутствие контента при наличии иконки, несогласованные aria-атрибуты. Это уже не вопрос синтаксиса TypeScript. Это вопрос архитектуры API.

Сильный вариант через discriminated union

import type { ReactNode } from "react";

type BaseButtonProps = {
  children: ReactNode;
  isLoading?: boolean;
  icon?: ReactNode;
};

type LinkButtonProps = BaseButtonProps & {
  kind: "link";
  href: string;
  onClick?: never;
};

type ActionButtonProps = BaseButtonProps & {
  kind: "action";
  onClick: () => void;
  href?: never;
};

type ButtonProps = LinkButtonProps | ActionButtonProps;

export function Button(props: ButtonProps) {
  if (props.kind === "link") {
    return (
      <a href={props.href} aria-busy={props.isLoading}>
        {props.icon}
        {props.children}
      </a>
    );
  }

  return (
    <button type="button" onClick={props.onClick} disabled={props.isLoading}>
      {props.icon}
      {props.children}
    </button>
  );
}

Почему этот ответ сильнее на интервью:

  • типы запрещают невозможную комбинацию href + onClick;
  • ветки рендера совпадают с ветками типовой модели;
  • автодополнение в JSX становится честнее;
  • компонент проще расширять без скрытых регрессий.

Эта техника особенно полезна там, где API компонента начинает напоминать библиотечный, например в формах или сложных контролах. В смежной теме это хорошо видно на разнице между controlled и uncontrolled компонентами.

Архитектура типовой модели: где живут DTO, domain и view model

На интервью по React и TypeScript часто спрашивают не напрямую, а через задачу: "У нас есть экран профиля, данные приходят с API, часть полей редактируется, часть зависит от роли пользователя. Как бы вы это типизировали?"

Сильный ответ не должен ограничиваться одним интерфейсом на 40 полей. Лучше показать слои:

  • ApiUserDto описывает то, что реально пришло по сети.
  • User описывает доменную модель, с которой работает приложение.
  • ProfileViewModel описывает форму, удобную конкретному экрану.

Поток данных обычно такой:

  1. Слой запроса получает сырые данные.
  2. Адаптер проверяет обязательные поля и преобразует DTO в доменную сущность.
  3. Компоненты получают уже нормализованную модель, а не сырой JSON.
  4. Форма редактирования строит локальную view model и валидирует только то, что относится к UI.

У этого подхода есть важные границы ответственности:

  • API-слой отвечает за доверие к сети и преобразование данных.
  • Доменные типы отвечают за инварианты бизнеса.
  • Компоненты отвечают за отображение и интерактивность.

Если эти слои смешать, появляется классическая проблема: UI начинает зависеть от нестабильного формата ответа бэкенда. Потом любое изменение поля в API приводит к изменениям во множестве React-компонентов. На production это заметно не только по коду, но и по времени отклика команды на изменения контракта.

Точка отказа здесь одна и та же: разработчик типизирует fetch(...).json() как "уже корректный User", хотя фактически он получил неизвестные данные. На интервью полезно прямо проговорить, что доверять данным из сети без проверки типа опасно.

Вопрос 2. Как типизировать состояние UI без impossible states

Один из лучших маркеров инженерной зрелости в TypeScript для React — умение не хранить несогласованные флаги.

Плохой вариант:

type UsersState = {
  isLoading: boolean;
  data: User[] | null;
  error: string | null;
};

С таким типом компонент легко попадает в нелепое состояние: isLoading === true, data !== null, error !== null. Формально тип это разрешает, а реальный UI потом приходится защищать тремя if.

Лучший вариант, union по статусу:

type User = {
  id: string;
  name: string;
  email: string;
};

type UsersState =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: User[] }
  | { status: "error"; message: string };

function UsersPanel({ state }: { state: UsersState }) {
  switch (state.status) {
    case "idle":
      return <p>Введите фильтр для поиска.</p>;
    case "loading":
      return <p>Загрузка...</p>;
    case "error":
      return <p role="alert">{state.message}</p>;
    case "success":
      return (
        <ul>
          {state.data.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      );
  }
}

Почему это ценят на интервью:

  • каждый статус имеет свой обязательный payload;
  • JSX становится прямым продолжением типовой модели;
  • реже появляются защитные проверки "на всякий случай";
  • reducer и тесты получаются компактнее.

Именно здесь удобно связать TypeScript с React-архитектурой: если состояние сложное, логичнее хранить его через useReducer, а не плодить несвязанные useState. Когда обсуждаете такой ответ, полезно показать связь с типизацией action и объяснить, почему reducer выигрывает в читаемости.

Прокачай React за 7 дней

20 вопросов и разборов по React Hooks.

Начать

Вопрос 3. interface или type

Это популярный вопрос, но сам по себе он малоинформативен. Полезный ответ не должен быть догматичным в стиле "всегда только interface" или "только type".

Сравнение подходов к типизации React API

КритерийinterfacetypeReact.FCDiscriminated union props
Базовые object propsУдобноУдобноКосвенноИзбыточно для простого случая
Union и compositionОграниченноОчень удобноНе решает задачуЛучший вариант
Generic-компонентыНормальноНормальноЧасто неудобноНормально, если не переусложнять
Явный контроль childrenДаДаЧасто скрыт за абстракциейДа
Масштабирование API компонентаХорошо для контрактовХорошо для алиасов и unionsЧасто проигрываетХорошо для режимов компонента

Практическое правило обычно такое:

  • interface хорошо подходит для читаемых object-контрактов и расширяемых сущностей;
  • type удобнее для union, intersection и составных алиасов;
  • React.FC не нужен по умолчанию;
  • discriminated union нужен там, где есть разные режимы работы компонента.

Сильная формулировка на интервью может звучать так: "Для простых props я возьму type или interface, что лучше читается по месту. Если компонент имеет взаимоисключающие режимы, перейду на union. React.FC не считаю дефолтом, потому что хочу явно контролировать контракт компонента и не усложнять generics".

Вопрос 4. Как типизировать generic-компонент

Этот вопрос часто позволяет отличить middle от уверенного junior. На словах почти все знают, что generics существуют, но на практике уже на первом переиспользуемом списке вывод типов перестаёт работать.

Типовой пример:

type ListProps<T> = {
  items: T[];
  getKey: (item: T) => string;
  renderItem: (item: T) => React.ReactNode;
  emptyText?: string;
};

export function List<T>({
  items,
  getKey,
  renderItem,
  emptyText = "Список пуст",
}: ListProps<T>) {
  if (items.length === 0) {
    return <p>{emptyText}</p>;
  }

  return (
    <ul>
      {items.map((item) => (
        <li key={getKey(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

Использование:

type Candidate = {
  id: string;
  fullName: string;
  grade: "junior" | "middle" | "senior";
};

const candidates: Candidate[] = [
  { id: "1", fullName: "Анна Иванова", grade: "middle" },
];

<List
  items={candidates}
  getKey={(candidate) => candidate.id}
  renderItem={(candidate) => (
    <span>
      {candidate.fullName} - {candidate.grade}
    </span>
  )}
/>;

Что здесь обычно проверяют:

  • не потерялся ли вывод T в JSX;
  • не спрятали ли вы generic под any;
  • не стал ли reusable-компонент слишком абстрактным ради самой абстракции.

Типичная ошибка кандидата — попытаться сделать "универсальный компонент на все случаи", который поддерживает 12 пропсов кастомизации и становится нечитаемым. На интервью лучше показать сдержанность: generic полезен, пока он сохраняет конкретный контракт, а не превращает API в конструктор из колбэков.

Вопрос 5. Как работать с unknown, any и данными API

Если интервьюер спрашивает про any, он часто проверяет не теорию, а инженерную дисциплину. any удобен как временное решение, но отключает значительную часть защиты TypeScript. Поэтому сильный ответ обычно такой: по умолчанию я предпочитаю unknown, а затем явно сужаю тип.

type ProductDto = {
  id: string;
  title: string;
  price: number;
};

function isProductDto(value: unknown): value is ProductDto {
  if (typeof value !== "object" || value === null) {
    return false;
  }

  const candidate = value as Record<string, unknown>;

  return (
    typeof candidate.id === "string" &&
    typeof candidate.title === "string" &&
    typeof candidate.price === "number"
  );
}

async function loadProduct(): Promise<ProductDto> {
  const response = await fetch("/api/product");
  const data: unknown = await response.json();

  if (!isProductDto(data)) {
    throw new Error("Некорректный ответ API");
  }

  return data;
}

Такой пример особенно хорошо работает в интервью, потому что показывает сразу несколько вещей:

  • вы не верите сети без проверки;
  • понимаете, где нужен runtime-check, а где достаточно compile-time типизации;
  • видите границу между TypeScript и реальным JavaScript.

Это особенно полезно в формах, фильтрах и списках, где устаревший или частично несовместимый ответ API легко приводит к ошибкам отображения. В практическом React-коде это напрямую связано с тем, как вы строите эффекты и отменяете запросы, поэтому рядом полезно помнить разбор сложных вопросов по React Forms.

Разбор производительности: где типы помогают, а где мешают

TypeScript не ускоряет рендер сам по себе, но может сильно влиять на архитектурные решения, которые уже отражаются на производительности.

Типы помогают, когда:

  • заставляют разделить локальное UI-состояние и server state;
  • убирают лишние ветки и защитные проверки в JSX;
  • позволяют построить точный contract-first API у компонента;
  • удерживают state machine в консистентном виде.

Типы мешают, когда:

  • generic и union настолько сложны, что компонент становится трудно сопровождать;
  • ради "идеальной строгости" вы размазываете типовую модель по пяти уровням alias;
  • команда начинает лечить производительность через типы, а не через профилирование.

Узкое место в React-приложении почти всегда не связано непосредственно с TypeScript. Чаще это слишком высоко поднятое состояние, нестабильные ссылки в props, тяжелые списки или неудачный поток обновлений. Поэтому на интервью полезно проговаривать: "Типы помогают удержать модель данных, но производительность я проверяю профилировщиком и границами рендера, а не надеждой на красивый alias".

Это хороший способ показать, что вы не подменяете диагностику общими словами про оптимизацию. Для такого разговора часто помогает и материал про поиск узких мест в React performance.

Production pitfalls

Ниже три ошибки, которые регулярно встречаются в реальных проектах и хорошо звучат на интервью, потому что у них есть наблюдаемые симптомы.

Типы совпадают с мечтой, а не с реальным API

Признак в логах и метриках:

  • всплеск ошибок рендера после релиза бэкенда;
  • Cannot read properties of undefined;
  • деградация пользовательского сценария на одном конкретном payload.

Последствие:

  • экран формально "собирается", но падает на живых данных.

Как поймать раньше:

  • не типизировать json() как готовую доменную сущность;
  • иметь runtime-проверку или адаптер на границе сети;
  • отдельно тестировать неполные и устаревшие payload.

Компонентный API допускает невозможные комбинации

Признак:

  • растут ветки if в компоненте;
  • в сторибуке или тестах появляются странные режимы "на всякий случай";
  • пропсы начинают конфликтовать по смыслу.

Последствие:

  • рост багов в edge-case и непредсказуемый JSX.

Как поймать раньше:

  • использовать union по режимам;
  • проектировать props через сценарии использования, а не через список полей.

Избыточная абстракция generic-компонентов

Признак:

  • новые разработчики боятся трогать компонент;
  • IDE подсказывает длинные цепочки сложных типов;
  • исправление одного бага требует менять общую абстракцию.

Последствие:

  • стоимость сопровождения растет быстрее, чем польза от переиспользования.

Как поймать раньше:

  • ограничивать generic реальным кейсом;
  • предпочитать конкретный контракт универсальности "на будущее".

Практики, которые усиливают React + TypeScript код

Архитектурные практики

  • Разделяйте DTO, domain model и view model.
  • Не храните невозможные состояния через набор независимых флагов.
  • Проектируйте props от сценариев использования, а не от списка опций.

Практики кода

  • Предпочитайте unknown вместо безусловного any.
  • Используйте discriminated unions там, где у компонента есть режимы.
  • Не берите React.FC по инерции, если он не добавляет ясности.

Наблюдаемость и тестирование

  • Проверяйте адаптеры данных отдельными тестами.
  • Для union-state полезны тесты на exhaustiveness: каждый статус должен быть обработан.
  • Для сложных компонентных API полезно иметь story на каждую законную ветку props.

Rollout и откат

  • Если меняете форму API или базовый reusable-компонент, выкатывайте поэтапно.
  • На критичных экранах держите возможность быстрого отката адаптера или новой модели props.

Частые ошибки

  1. Типизировать все через any, чтобы "не мешал компилятор".
  2. Использовать React.FC как обязательный стандарт без аргументов.
  3. Путать compile-time гарантию с runtime-проверкой данных.
  4. Хранить loading, error и data разрозненно и потом латать невозможные состояния.
  5. Делать generic-компонент раньше, чем появился второй реальный кейс переиспользования.
  6. Забывать, что хороший типовой контракт должен упрощать JSX, а не усложнять его.

Если хотите отдельно посмотреть, где кандидаты чаще всего сыпятся на формулировках, полезен и разбор типичных ошибок на React-интервью.

Как отвечать на интервью по React и TypeScript

Рабочая структура ответа почти всегда такая:

  1. Назовите контекст: библиотечный компонент, экран формы, список из API, reusable-hook.
  2. Покажите типовую модель.
  3. Объясните, какую ошибку она предотвращает.
  4. Коротко скажите о компромиссе.
  5. Дайте маленький пример.

Пример сильного ответа на вопрос "зачем discriminated union в React":

Я использую discriminated union, когда компонент имеет несколько взаимоисключающих режимов. Например, загрузка, успех и ошибка у списка пользователей. Такой тип не дает состоянию одновременно быть и loading, и success. JSX становится проще, reducer понятнее, а новые ветки легче тестировать. Компромисс один: модель нужно продумать заранее, но на сложном UI это почти всегда окупается.

Пример сильного ответа на вопрос "почему не any":

Any помогает локально, но подрывает доверие к типовой системе в целом. На границе сети я предпочитаю unknown, затем делаю type guard или адаптер. Так я отделяю непроверенные данные от доменной модели и не размываю контракт компонента.

Именно такой формат обычно производит лучшее впечатление, чем ответ уровня "так принято". Вы показываете не только знание TypeScript, но и умение принимать инженерные решения в React-коде.

Подготовка к React собеседованию с упором на реальные ответы

Разберите вопросы по React и TypeScript в формате настоящего интервью: архитектура компонентов, типизация, производительность и устные ответы без шаблонных заготовок.

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

FAQ

Что важнее на интервью по React и TypeScript: синтаксис или мышление?

Для junior часто проверяют базовый синтаксис, но уже на уровне strong junior и middle важнее понимание причин и следствий принимаемых решений. Интервьюеру нужен не набор определений, а понимание, почему типовая модель делает компонент безопаснее и дешевле в поддержке.

Когда на интервью уместно использовать React.FC?

Когда команда уже стандартизировала такой стиль и от него есть реальная польза в кодовой базе. Но как дефолтный ответ это обычно слабее, чем явная типизация props и возвращаемого значения компонента.

Стоит ли на собеседовании сразу предлагать generics?

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

Как объяснить разницу между unknown и any?

Any отключает проверки и позволяет делать с данными что угодно. Unknown заставляет сначала доказать форму значения через narrowing, поэтому лучше подходит для границ системы, например для ответа API.

Какие вопросы по React и TypeScript самые показательные для middle?

Обычно это типизация state machine, reusable-компонентов, refs, generic-hooks, обработка API-данных и аргументация, почему конкретный контракт props безопасен в production, а не только удобен при написании кода.

Итоги

Частые вопросы на интервью по React и TypeScript редко сложны сами по себе. Сложность начинается в момент, когда нужно защитить решение: почему этот контракт props не ломается, почему это состояние не допускает невозможных комбинаций, почему этому ответу API нельзя доверять без сужения типа, и где типы действительно помогают React-коду, а где только усложняют код без реальной пользы.

Если коротко, сильный кандидат по React и TypeScript делает три вещи: строит адекватную модель данных, не маскирует риски через any и умеет объяснить компромисс решения на языке production. Это и есть та разница, которую интервьюер обычно замечает быстрее всего.

Больше вопросов в Telegram

Ежедневные разборы и реальные кейсы с интервью.

Подписаться

Автор

Lexicon Team

Читайте также