React patterns, которые спрашивают на senior интервью: не определения, а архитектурные компромиссы
Разбираем React patterns для senior интервью: HOC, Render Props, Compound Components, controlled/uncontrolled, headless API и критерии выбора.
- Введение
- Какие React patterns обычно спрашивают у senior
- Что именно проверяют на senior интервью
- 1. Понимание контракта компонента
- 2. Умение обсуждать компромиссы
- 3. Связь паттерна с рендер-моделью React
- 4. Продакшен-мышление
- 5. Эволюция решения
- Архитектурная рамка: как выбирать паттерн, а не спорить вкусами
- Контекст задачи
- Схема компонентов
- Поток управления
- Узкие места
- Pattern 1: Higher-Order Components
- Pattern 2: Render Props
- Pattern 3: Compound Components
- Pattern 4: Controlled vs Uncontrolled
- Pattern 5: Headless Components и Custom Hooks
- Код-пример 1: плохой и хороший контракт сложного виджета
- Код-пример 2: HOC против hook в задаче авторизации
- Код-пример 3: как проектировать headless API без лишнего шума
- Сравнение паттернов
- Проблемы в production: как паттерны начинают вредить
- 1. Паттерн выбирают раньше, чем понимают контракт
- 2. Производительность деградирует не из-за React, а из-за формы API
- 3. Паттерн ухудшает наблюдаемость
- 4. Развитие design system становится хрупким
- Разбор производительности: где находится реальная цена
- Практики, которые позволяют дать сильный ответ на senior-уровне
- Архитектурные практики
- Практики кода
- Практики наблюдаемости
- Практики тестирования
- Практики rollout и rollback
- Частые ошибки
- Как отвечать на интервью
- FAQ
- Какие React patterns обязательно знать перед senior интервью?
- Почему на senior интервью до сих пор спрашивают HOC и Render Props?
- Какой React pattern чаще всего выбирают неправильно?
- Что говорить про производительность, если спрашивают про паттерны?
- Нужно ли упоминать, когда паттерн не подходит?
- Итоги
Введение
Когда на 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 потребителю.
Поток управления
Сильный ответ всегда показывает поток события:
- Пользовательское действие меняет состояние.
- Состояние проходит через выбранный контракт.
- Зависимые части дерева получают обновление.
- UI реагирует предсказуемо.
- Ошибки, деградация и 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-мышления: меньше магии, понятнее контракт, проще тестировать.
Сравнение паттернов
| Критерий | HOC | Render Props | Compound Components | Headless Hook |
|---|---|---|---|---|
| Главная задача | Навесить поведение на компонент | Отделить поведение от рендера | Построить декларативный API сложного виджета | Переиспользовать поведение без навязывания UI |
| Читаемость | Может ухудшаться из-за оберток | Может ухудшаться из-за вложенных функций | Обычно хорошая при четких ролях | Обычно лучшая внутри приложения |
| Производительность | Зависит от числа оберток и проп-дрифта | Чувствителен к нестабильным функциям | Чувствителен к ширине Context | Зависит от архитектуры hook и частоты обновлений |
| Тестируемость | Хороша для behavior-wrapper тестов | Хороша для контрактных provider тестов | Хороша для интеграционных сценариев | Хороша для unit и интеграционных тестов |
| Эволюция API | Может запутываться при глубокой композиции | Растет цена читаемости | Хорошо масштабируется для сложных ролей | Хорошо масштабируется для внутренней логики |
| Когда выбирать | Legacy, cross-cutting concerns, быстрый адаптер | Нужен гибкий публичный render-контракт | Нужен составной UI с ролями | Нужна чистая логика без общего DOM-контракта |
| Главный риск | Wrapper hell | Callback 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-ответа выглядит так:
- Сначала назовите задачу, которую решает паттерн.
- Затем обозначьте, где находится источник истины и как устроен контракт.
- После этого проговорите 2-3 компромисса: читаемость, производительность, тестируемость, эволюция API.
- Добавьте пример, где вы сознательно не выбрали этот паттерн.
- Завершите сигналами из продакшена: по каким метрикам или симптомам вы поняли, что решение работает или требует пересмотра.
Пример сильного ответа:
"Если меня спрашивают про 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
Читайте также
frontend
Как проектировать масштабируемый React frontend: архитектура, состояние и границы модулей
Практический разбор того, как проектировать масштабируемый React frontend: модули, state management, performance, типичные ошибки и сильный ответ на интервью.
frontend
System design для frontend разработчика: как мыслить системно, а не только компонентами
Практический разбор system design для frontend разработчика: границы ответственности, данные, производительность, ошибки и сильный ответ на интервью.
frontend
Feature-Sliced Design для React проектов: когда FSD помогает, а когда усложняет код
Практический разбор Feature-Sliced Design для React проектов: слои, public API, правила зависимостей, типичные ошибки, производительность и ответы для интервью.