React

Паттерны компонентов

Компонент Modal начинался с трёх пропсов. Через год у него их тридцать: showHeader, headerIcon, footerButtons, onSecondaryClick, hideCloseButton и так далее. Каждое новое требование добавляет ещё один флаг, внутри растёт лес условий, а вызвать его правильно невозможно без чтения исходника. Это классический тупик конфигурации. Паттерны компонентов предлагают другой выход: вместо нагромождения пропсов дать пользователю собирать поведение из кусочков. Те же библиотеки, которыми пользуются ежедневно - Radix, React Router, формы - построены именно так.

  • Headless-библиотеки Radix UI и React Aria: compound components вроде Select.Trigger и Select.Content вместо десятков пропсов
  • Таблицы и списки, где строку и ячейку задаёт render prop, а данные приходят сверху
  • Дизайн-системы, где Tabs, Accordion и Menu собираются из под-компонентов, а не настраиваются флагами
  • Разделение container и presentational в крупных приложениях ради тестируемости и переиспользования вёрстки
  • Кастомные хуки как способ вынести логику списка или формы и переиспользовать её в разных представлениях

Предварительные знания

  • Компоненты, пропсы и проп children
  • Кастомные хуки и вынос логики из компонента
  • useContext для передачи значения вниз по дереву
  • Кастомные хуки

От миксинов к хукам: эволюция переиспользования

Переиспользование логики в React прошло несколько эпох. Сначала были миксины классов, которые путали источники методов и конфликтовали именами. На смену пришли render props и компоненты высшего порядка (HOC): логика выносилась в компонент, а результат отдавался через функцию-ребёнка или обёртку. У HOC обнаружились свои беды - ад вложенности обёрток и неясное происхождение пропсов. В 2019 хуки (React 16.8) дали прямой способ делить логику без лишних узлов в дереве. Сегодня render props и compound components остаются для композиции разметки, а логику чаще всего выносят в кастомные хуки.

Compound components и композиция вместо конфигурации

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

Под-компоненты не получают активную вкладку через пропсы - они читают её из общего Context, который заводит корневой Tabs. Разметку и порядок задаёт вызывающий, а синхронизацию состояния берёт на себя семейство. Добавить иконку или произвольный элемент между вкладками можно прямо в JSX, не трогая API компонента.

Композиция вместо конфигурации это общий принцип за этим паттерном. Гибкость даётся через вложенность и children, а не через рост числа пропсов. Поэтому Radix UI и React Aria экспонируют именно семейства под-компонентов, оставляя разметку и стиль на усмотрение приложения.

Какую проблему решают compound components по сравнению с компонентом на множестве пропсов?

Container и presentational, render props

Разделение container и presentational отделяет два разных дела. Презентационный компонент отвечает только за то, как это выглядит: получает данные и колбэки пропсами и рисует разметку, не зная, откуда данные взялись. Контейнер отвечает за то, что показывать: грузит данные, держит состояние и передаёт всё вниз. Презентационный компонент чисто тестируется и переиспользуется, потому что не привязан к источнику данных.

Render props решают другую задачу - переиспользование логики при свободе разметки. Компонент инкапсулирует поведение, а как отрисовать результат, решает вызывающий через функцию-ребёнка (children как функция). Один и тот же источник данных можно показать списком, таблицей или карточками, не дублируя логику.

С приходом хуков многие задачи, которые раньше решались render props и HOC, удобнее решать кастомным хуком: логика выносится без лишних узлов в дереве. Render props остаются хороши там, где наружу нужно отдать именно управление разметкой конкретного элемента.

В чём смысл разделения на container и presentational компоненты?

Паттерн кастомного хука и headless-подход

Паттерн кастомного хука доводит идею разделения до предела: вся логика - состояние, эффекты, обработчики - выносится в хук, а компонент остаётся почти чистой отрисовкой. В отличие от render props и HOC, хук не добавляет узлов в дерево и не порождает обёрток. Логику можно переиспользовать в любом представлении, просто вызвав хук.

Это основа headless-подхода: библиотека отдаёт логику и состояние хуком, а всю разметку и стиль пишет приложение. Так устроены React Hook Form (хук useForm) и многие компоненты Radix и TanStack: поведение и доступность внутри, внешний вид целиком на стороне разработчика.

  • Render props / HOC — Логика выносится в компонент, результат отдаётся функцией-ребёнком или обёрткой. Добавляет узлы в дерево, возможна вложенность обёрток.
  • Кастомный хук — Логика выносится в хук без узлов в дереве. Переиспользуется вызовом, не порождает обёрток. Дефолтный способ делить логику сегодня.

Паттерны не самоцель. Compound components и render props оправданы, когда компонент действительно переиспользуется в разных формах. Для одноразового UI они добавляют сложность без выгоды. Принцип композиции вместо конфигурации применяют там, где гибкость реально нужна, а не везде подряд.

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

Связь с другими темами

Этот урок про паттерны композиции. Рядом стоят соседние темы:

  • Кастомные хуки — Базовый механизм выноса логики, на котором держится паттерн кастомного хука и headless-компоненты
  • TypeScript и React — Дженерик-компоненты и типизация children делают эти паттерны безопасными в большой кодовой базе

Итог

  • Compound components разбивают сложный компонент на семейство под-компонентов, общающихся через Context, вместо десятков пропсов
  • Разделение container и presentational отделяет логику и данные от чистой вёрстки, упрощая тесты и переиспользование
  • Render props и children-as-function передают наружу управление разметкой: компонент даёт данные, а как их показать решает вызывающий
  • Паттерн кастомного хука выносит логику в хук, оставляя представлению только отрисовку - основа headless-подхода
  • Композиция вместо конфигурации это сквозной принцип: собирать поведение из частей предпочтительнее, чем накапливать булевы флаги
  • Современные библиотеки (Radix, React Aria) построены на этих паттернах, поэтому их узнавание ускоряет работу с экосистемой

Связанные уроки

  • rc-16-custom-hooks — Паттерн кастомного хука это базовый способ вынести логику, на котором стоят остальные паттерны
  • rc-41-typescript-react — Типизация дженерик-компонентов и пропсов делает эти паттерны безопасными в больших кодовых базах
Паттерны компонентов

0

1

Войти