React patterns, которые спрашивают на senior интервью: не определения, а архитектурные компромиссы

Разбираем React patterns для senior интервью: HOC, Render Props, Compound Components, controlled/uncontrolled, headless API и критерии выбора.

25 марта 2026 г.17 минLexicon Team

Введение

Когда на senior-собеседовании спрашивают React patterns, почти никогда не хотят услышать набор определений из учебника. Интервьюеру важнее другое: умеете ли вы выбрать паттерн под ограничения продукта, объяснить цену этого выбора и предсказать, где решение начнет ломаться.

Поэтому вопрос "что такое Render Props" на senior уровне обычно превращается в "почему вы не выбрали custom hook", а вопрос про Compound Components быстро доходит до API-дизайна, Context API, границ ответственности и лишних ререндеров.

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

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

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

Подписаться

Какие React patterns обычно спрашивают у senior

На senior уровне чаще всего обсуждают не "паттерны ради паттернов", а семейства решений:

  • Higher-Order Components как способ расширять поведение и инвертировать зависимости;
  • Render Props как контракт "логика отдельно, рендер отдельно";
  • Compound Components как паттерн проектирования API сложного виджета;
  • controlled/uncontrolled как выбор источника истины;
  • headless-компоненты и custom hooks как более современный способ отделить поведение от внешнего вида;
  • provider-паттерны и разбиение Context, когда речь доходит до shared UI и design system.

Сильный кандидат не пытается доказать, что один подход "лучше всегда". Он показывает карту решений: в каких условиях паттерн снижает сложность, а в каких просто переносит ее в другое место.

Что именно проверяют на senior интервью

Обычно проверяют пять вещей.

1. Понимание контракта компонента

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

2. Умение обсуждать компромиссы

Senior-ответ должен содержать развилку:

  • что выигрываем в читаемости;
  • что теряем в производительности;
  • как меняется тестируемость;
  • как это влияет на onboarding команды;
  • можно ли откатить или заменить решение без массового переписывания экранов.

3. Связь паттерна с рендер-моделью React

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

4. Продакшен-мышление

Вопрос быстро переходит к наблюдаемости: как дебажить, как профилировать, где ставить guardrails, какой сигнал в метриках подскажет, что паттерн перестал работать.

5. Эволюция решения

Senior-инженер должен уметь объяснить, как паттерн переживет рост продукта: новые сценарии, migration path, обратную совместимость и ограничения design system.

Архитектурная рамка: как выбирать паттерн, а не спорить вкусами

Перед выбором паттерна полезно пройти короткий чек-лист.

Контекст задачи

Нужно понять:

  • компонент внутренний или библиотечный;
  • логика локальная или разделяется между командами;
  • API должен быть жестким или расширяемым;
  • основной риск в гибкости, скорости рендера или сложности поддержки.

Схема компонентов

У любого решения есть минимум три слоя:

  • источник состояния;
  • механизм доставки состояния и действий до UI;
  • слой, который определяет конечную структуру интерфейса.

React patterns в основном различаются именно вторым и третьим слоями. HOC оборачивает компонент. Render Props передает функцию. Compound Components раскладывает интерфейс на роли. Headless-подход выносит поведение в hook и оставляет UI потребителю.

Поток управления

Сильный ответ всегда показывает поток события:

  1. Пользовательское действие меняет состояние.
  2. Состояние проходит через выбранный контракт.
  3. Зависимые части дерева получают обновление.
  4. UI реагирует предсказуемо.
  5. Ошибки, деградация и rollback остаются управляемыми.

Узкие места

Главные точки отказа почти всегда одинаковые:

  • слишком широкий контракт;
  • неявная связность между частями;
  • рост стоимости рендера;
  • невозможность локально изменить поведение без массовых побочных эффектов.

Именно поэтому тема паттернов тесно связана с профилированием React-приложений и поиском узких мест.

Pattern 1: Higher-Order Components

Higher-Order Components, или HOC, часто воспринимают как "старый паттерн до hooks". На senior интервью это ловушка. Хороший ответ не в том, чтобы списать HOC как legacy, а в том, чтобы объяснить, какую задачу он решал и почему иногда до сих пор остается уместным.

HOC полезен, когда нужно:

  • внедрить кросс-секционное поведение в набор компонентов;
  • централизовать авторизацию, feature flag, telemetry или error boundary;
  • адаптировать старое class-based или mixed codebase без тотального рефакторинга.

Слабые места HOC:

  • обертки усложняют дерево компонентов;
  • труднее дебажить источник пропсов и side effects;
  • растет риск конфликтов имен пропсов и "протекания" лишнего API.

Если нужно более детально освежить механизм, есть отдельный разбор Higher-Order Components в React. Но на senior интервью полезнее не объяснять синтаксис, а показать критерий выбора: HOC хорош там, где нужно навесить поведение на существующий компонент без изменения его внутренней реализации.

Pattern 2: Render Props

Render Props часто спрашивают вместе с вопросом "почему не hook". Это хороший маркер senior-уровня: интервьюер проверяет, умеете ли вы обсуждать не паттерн сам по себе, а форму контракта.

Render Props уместен, когда:

  • поведение должно быть переиспользуемым;
  • компонент-поставщик не должен навязывать DOM-структуру;
  • внешний код должен получить контроль над тем, как рисуется результат.

Проблема в том, что паттерн легко превращается в вложенный лес функций. Тогда логика действительно переиспользуется, но поддержка интерфейса дорожает. Подробный пример уже разобран в статье про Render Props в React.

Senior-ответ здесь звучит примерно так: "Сегодня я не выберу Render Props по умолчанию, но если мне нужен публичный контракт между provider-компонентом и несколькими UI-реализациями, этот паттерн все еще полезен, особенно в библиотечном коде".

Pattern 3: Compound Components

Когда на интервью говорят про API сложного виджета, очень быстро всплывают Compound Components. Причина простая: это паттерн, на котором хорошо видно, умеет ли кандидат проектировать интерфейсы для людей, а не просто писать JSX.

Сильные стороны подхода:

  • структура JSX совпадает со структурой интерфейса;
  • роли элементов становятся явными;
  • API масштабируется спокойнее, чем список из двадцати пропсов.

Слабые стороны:

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

Хороший пример и более глубокий разбор компромисса есть в материале про Compound Components в React. На senior интервью полезно проговорить не только преимущества, но и цену: такой API хорошо работает, пока инварианты и роли остаются понятными.

Pattern 4: Controlled vs Uncontrolled

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

Суть вопроса не в input, а в том, где находится источник истины:

  • внутри компонента;
  • выше по дереву;
  • или в смешанном режиме с defaultValue и возможностью внешнего контроля.

Именно на этом месте проверяют зрелость API-дизайна. Если компонент живет в design system, часто требуются оба режима (controlled/uncontrolled). Если это внутренний одноразовый экран, двойной контракт может быть лишней сложностью. Смежный разбор есть в статье Controlled vs Uncontrolled в React.

Pattern 5: Headless Components и Custom Hooks

Хотя "headless" формально не всегда называют отдельным React pattern, на senior интервью эта тема звучит постоянно. Почему? Потому что headless-подход показывает, умеете ли вы разделять поведение, состояние и визуальный слой.

Частая эволюция выглядит так:

  • сначала команда пишет компонент с жесткой разметкой;
  • потом появляется второй дизайн-сценарий;
  • потом UI распухает от пропсов;
  • дальше логика выносится в custom hook или headless provider.

Это зрелый путь. Важно понимать, что headless-архитектура не бесплатна. Она требует дисциплины:

  • четкого контракта;
  • хорошей документации;
  • тестов на поведение;
  • контроля рендеров, если hook или provider обновляются часто.

Именно здесь senior-инженер должен уметь связать выбор паттерна с мемоизацией и стоимостью рендеров.

Код-пример 1: плохой и хороший контракт сложного виджета

Плохой API чаще всего выглядит так: один компонент знает слишком много о структуре интерфейса и получает длинный список пропсов.

type ModalProps = {
  title: string;
  description?: string;
  primaryActionText: string;
  secondaryActionText?: string;
  onPrimaryAction: () => void;
  onSecondaryAction?: () => void;
  showCloseIcon?: boolean;
  footerAlign?: "start" | "center" | "end";
  bodyPadding?: "sm" | "md" | "lg";
  isOpen: boolean;
  onOpenChange: (next: boolean) => void;
  children: React.ReactNode;
};

export function Modal(props: ModalProps) {
  // ...
}

Сначала такой контракт кажется удобным. Через несколько релизов выясняется, что:

  • часть экранов хочет собственный footer;
  • часть экранов требует асинхронное подтверждение;
  • кому-то нужен другой layout заголовка;
  • а кто-то хочет убрать actions полностью.

Тогда API перестает быть компонентом и превращается в конфигуратор. Более устойчивый вариант:

type DialogRootProps = {
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (next: boolean) => void;
  children: React.ReactNode;
};

export function Dialog({ open, defaultOpen, onOpenChange, children }: DialogRootProps) {
  const [internalOpen, setInternalOpen] = useState(defaultOpen ?? false);
  const isControlled = open !== undefined;
  const actualOpen = isControlled ? open : internalOpen;

  const setOpen = (next: boolean) => {
    if (!isControlled) setInternalOpen(next);
    onOpenChange?.(next);
  };

  return (
    <DialogContext.Provider value={{ open: actualOpen, setOpen }}>
      {children}
    </DialogContext.Provider>
  );
}

Dialog.Trigger = function DialogTrigger({ children }: { children: React.ReactNode }) {
  const { setOpen } = useDialogContext();
  return <button onClick={() => setOpen(true)}>{children}</button>;
};

Dialog.Content = function DialogContent({ children }: { children: React.ReactNode }) {
  const { open } = useDialogContext();
  if (!open) return null;
  return <div role="dialog">{children}</div>;
};

Почему второй вариант сильнее для senior-разговора:

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

Код-пример 2: HOC против hook в задаче авторизации

На интервью любят задачу "как ограничить доступ к экрану". Здесь хорошо видно, когда HOC еще уместен, а когда лучше hook.

type WithPermissionOptions = {
  permission: string;
};

export function withPermission<P>(
  Component: React.ComponentType<P>,
  options: WithPermissionOptions
) {
  function Wrapped(props: P) {
    const user = useCurrentUser();

    if (!user.permissions.includes(options.permission)) {
      return <AccessDenied />;
    }

    return <Component {...props} />;
  }

  Wrapped.displayName = `withPermission(${Component.displayName ?? Component.name ?? "Component"})`;
  return Wrapped;
}

Такой HOC полезен, если:

  • нужно быстро адаптировать существующий парк страниц;
  • поведение должно подключаться декларативно;
  • важно централизованно навесить guard и fallback.

Но если экран уже на функциональных компонентах и нужно тонко управлять состояниями, hook часто проще:

export function usePermission(permission: string) {
  const user = useCurrentUser();
  return user.permissions.includes(permission);
}

export function BillingPage() {
  const canViewBilling = usePermission("billing.read");

  if (!canViewBilling) {
    return <AccessDenied />;
  }

  return <BillingDashboard />;
}

Senior-ответ здесь не в том, чтобы "выбрать hooks, потому что они современнее". Сильный ответ: HOC хорош для кросс-срезового расширения существующих компонентов, hook хорош для локальной логики и явного управления внутри компонента.

Код-пример 3: как проектировать headless API без лишнего шума

type UseSelectOptions<T> = {
  items: T[];
  getKey: (item: T) => string;
  onChange?: (item: T) => void;
};

export function useSelect<T>({ items, getKey, onChange }: UseSelectOptions<T>) {
  const [activeKey, setActiveKey] = useState<string | null>(null);

  const activeItem = items.find((item) => getKey(item) === activeKey) ?? null;

  const select = (item: T) => {
    const key = getKey(item);
    setActiveKey(key);
    onChange?.(item);
  };

  return {
    activeItem,
    isSelected: (item: T) => getKey(item) === activeKey,
    select,
  };
}

export function CountrySelect({ countries }: { countries: Country[] }) {
  const select = useSelect({
    items: countries,
    getKey: (country) => country.code,
  });

  return (
    <ul>
      {countries.map((country) => (
        <li key={country.code}>
          <button
            aria-pressed={select.isSelected(country)}
            onClick={() => select.select(country)}
          >
            {country.name}
          </button>
        </li>
      ))}
    </ul>
  );
}

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

Сравнение паттернов

КритерийHOCRender PropsCompound ComponentsHeadless Hook
Главная задачаНавесить поведение на компонентОтделить поведение от рендераПостроить декларативный API сложного виджетаПереиспользовать поведение без навязывания UI
ЧитаемостьМожет ухудшаться из-за обертокМожет ухудшаться из-за вложенных функцийОбычно хорошая при четких роляхОбычно лучшая внутри приложения
ПроизводительностьЗависит от числа оберток и проп-дрифтаЧувствителен к нестабильным функциямЧувствителен к ширине ContextЗависит от архитектуры hook и частоты обновлений
ТестируемостьХороша для behavior-wrapper тестовХороша для контрактных provider тестовХороша для интеграционных сценариевХороша для unit и интеграционных тестов
Эволюция APIМожет запутываться при глубокой композицииРастет цена читаемостиХорошо масштабируется для сложных ролейХорошо масштабируется для внутренней логики
Когда выбиратьLegacy, cross-cutting concerns, быстрый адаптерНужен гибкий публичный render-контрактНужен составной UI с ролямиНужна чистая логика без общего DOM-контракта
Главный рискWrapper hellCallback hellНеявная связностьРазъезд контрактов между hook и UI

Проблемы в production: как паттерны начинают вредить

1. Паттерн выбирают раньше, чем понимают контракт

Это самая дорогая ошибка. Команда сначала решает "делаем compound API", а потом пытается подогнать под него задачу. В результате инварианты остаются неясными, а любая новая фича требует исключений.

Признаки:

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

2. Производительность деградирует не из-за React, а из-за формы API

Если Context слишком широкий, render-функции нестабильны, а provider обновляется часто, паттерн начинает тянуть лишние ререндеры по дереву. Это обычно видно в профайлере раньше, чем в жалобах пользователей.

Признаки:

  • рост commit duration в React Profiler;
  • высокая цена простого клика или ввода;
  • локальное изменение состояния триггерит обновление слишком большого поддерева.

3. Паттерн ухудшает наблюдаемость

Это особенно заметно в HOC и provider-обертках. Когда поведение слишком глубоко спрятано, команде сложно ответить на базовые вопросы:

  • где именно произошел отказ;
  • какой контракт был нарушен;
  • какая обертка повлияла на итоговый UI;
  • как быстро локализовать регрессию.

4. Развитие design system становится хрупким

Сначала API кажется красивым. После приходит accessibility, затем несколько продуктовых потоков, потом A/B варианты, и наконец требования к аналитике и rollback. Если паттерн не держит эти нагрузки, система начинает расти через исключения, а не через модель.

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

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

Начать

Разбор производительности: где находится реальная цена

На senior интервью полезно отдельно проговорить, что у React patterns нет "врожденной" медленности. Медленной становится конкретная реализация.

На что смотреть:

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

Типичные компромиссы:

  • Render Props дает гибкость, но может увеличить стоимость рендера через нестабильные функции;
  • Compound Components улучшают API, но могут расширить зону подписки через Context;
  • headless hook упрощает UI, но может размазать контракт между несколькими слоями.

Когда оптимизация оправдана:

  • интерактивность деградирует на реальных пользовательских сценариях;
  • компонент массово используется на критических страницах;
  • метрики вроде INP или profiling traces показывают устойчивый регресс.

Когда оптимизация преждевременна:

  • изменений мало, а компонент рендерится редко;
  • проблема обсуждается без профилировщика;
  • команда начинает мемоизировать все подряд вместо упрощения модели данных.

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

Практики, которые позволяют дать сильный ответ на senior-уровне

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

  • Начинайте с модели состояния и инвариантов, а не с синтаксиса паттерна.
  • Проектируйте API так, как будто его будет поддерживать другая команда.
  • Думайте о migration path еще до первой публикации компонента.

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

  • Делайте контракты короткими и явно типизированными.
  • Не смешивайте транспортную логику, состояние и презентационный слой без необходимости.
  • Делайте ошибки интеграции явными, а не "тихо некорректными".

Практики наблюдаемости

  • Логируйте переходы ключевых состояний в shared-компонентах.
  • Считайте стоимость взаимодействий на критических сценариях.
  • Добавляйте дешевые сигналы деградации до того, как проблема попадет в саппорт.

Практики тестирования

  • Тестируйте не только happy path, но и ошибочное использование API.
  • Покрывайте controlled/uncontrolled сценарии отдельно.
  • Для design system проверяйте обратную совместимость контрактов.

Практики rollout и rollback

  • Выпускайте новые общие паттерны через ограниченный набор потребителей.
  • Держите fallback-сценарий, если общий контракт окажется слишком узким.
  • Не расширяйте shared API в ответ на единичный edge case без проверки общего паттерна.

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

  • Говорить, что старые паттерны "больше не нужны", вместо объяснения их зоны применимости.
  • Описывать паттерн как синтаксический прием, а не как форму контракта.
  • Игнорировать вопрос "где источник истины" при обсуждении controlled/uncontrolled.
  • Выбирать compound API там, где достаточно массива данных и простого .map.
  • Спорить о производительности без профилировщика и реальных сценариев.
  • Не учитывать, как решение переживет рост design system и новые продуктовые команды.

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

Рабочая структура senior-ответа выглядит так:

  1. Сначала назовите задачу, которую решает паттерн.
  2. Затем обозначьте, где находится источник истины и как устроен контракт.
  3. После этого проговорите 2-3 компромисса: читаемость, производительность, тестируемость, эволюция API.
  4. Добавьте пример, где вы сознательно не выбрали этот паттерн.
  5. Завершите сигналами из продакшена: по каким метрикам или симптомам вы поняли, что решение работает или требует пересмотра.

Пример сильного ответа:

"Если меня спрашивают про Render Props или Compound Components, я сначала разделяю задачи. Если нужен гибкий публичный контракт между поведением и UI, могу выбрать Render Props. Если строю составной виджет для design system, чаще смотрю в сторону Compound Components с четкими ролями и controlled/uncontrolled режимами. Но выбор подтверждаю не вкусом, а стоимостью поддержки, профилировкой и тем, насколько легко другим командам безопасно расширять API".

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

Потренируйте React patterns в формате senior-собеседования

Практика по React с вопросами про API-дизайн, patterns, производительность и архитектурные компромиссы, которые реально обсуждают на технических интервью.

Перейти к практике

FAQ

Какие React patterns обязательно знать перед senior интервью?

Минимум стоит уверенно понимать HOC, Render Props, Compound Components, controlled/uncontrolled режимы и headless/custom hook подход. Но важнее не список, а умение сравнивать их по контракту и цене сопровождения.

Почему на senior интервью до сих пор спрашивают HOC и Render Props?

Потому что через них удобно проверять понимание композиции и эволюции React. Эти паттерны показывают, понимает ли кандидат, как разделять поведение и представление, а не просто пользуется современным API.

Какой React pattern чаще всего выбирают неправильно?

Чаще всего переоценивают Compound Components и недооценивают controlled/uncontrolled контракт. Команда видит красивый JSX, но поздно замечает, что API стал неявным и плохо расширяемым.

Что говорить про производительность, если спрашивают про паттерны?

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

Нужно ли упоминать, когда паттерн не подходит?

Да. Для senior уровня это обязательно. Умение отказаться от паттерна в неподходящем контексте ценится выше, чем умение красиво его определить.

Итоги

React patterns на senior интервью проверяют не энциклопедические знания, а качество инженерного выбора. Сильный кандидат показывает, что умеет работать с API как с контрактом, видит источник истины, понимает влияние на рендер-модель и заранее думает о цене сопровождения.

Поэтому лучший способ готовиться к таким вопросам не зубрить определения, а учиться объяснять развилку: почему в одном случае уместен HOC, в другом headless hook, в третьем compound API, а в четвертом вообще не нужен никакой "паттерн", кроме простого компонента. Если в ответе есть задача, компромисс, production-сигналы и критерий замены решения, это уже звучит как senior.

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

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

Подписаться

Автор

Lexicon Team

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