Урок по треку React 7 мин

Паттерны компонентов: композиция, хуки, compound

Композиция, кастомные хуки, составные компоненты и render props.

Паттерны спрашивают, чтобы понять: умеешь ли ты проектировать компонент, который удобно переиспользовать, а не только «чтобы работало».

Композиция через children и слоты

Главный паттерн React: компонент не знает, что внутри, он даёт место.

function Card({ title, actions, children }) {
  return (
    <section className="card">
      <header>{title}{actions}</header>
      <div>{children}</div>
    </section>
  );
}

<Card title="Заказ" actions={<Button>Удалить</Button>}>
  <OrderDetails />
</Card>

children и пропсы-элементы (слоты) убирают десятки флагов вида showDeleteButton, deleteButtonText. Бонус: элементы, переданные через children, создаёт родитель, поэтому при смене состояния внутри Card они не перерисуются заново.

Кастомные хуки

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

function useToggle(initial = false) {
  const [on, setOn] = useState(initial);
  const toggle = useCallback(() => setOn((v) => !v), []);
  return [on, toggle];
}

Важно: два компонента, вызвавшие useToggle, получают независимые состояния. Хук делится логикой, а не данными. Общие данные дают Context или стор.

Кастомные хуки почти полностью вытеснили HOC и render props.

Render props

Компонент получает функцию и вызывает её, отдавая данные:

<DataLoader url="/api/users" render={(users) => <UserList users={users} />} />

Сейчас встречается в библиотеках (например, виртуальные списки, где строку рендерит ваша функция). Для своей логики обычно лучше хук.

HOC (компонент высшего порядка)

Функция, которая принимает компонент и возвращает новый: withAuth(Page). Минусы: непрозрачно, откуда пришёл пропс; конфликты имён пропсов; «ад обёрток» в DevTools. Нельзя создавать HOC внутри рендера: каждый раз получится новый тип компонента, и React размонтирует поддерево со всем состоянием.

Compound components

Набор компонентов, которые работают вместе и делят состояние через Context:

<Tabs defaultValue="a">
  <Tabs.List>
    <Tabs.Tab value="a">Профиль</Tabs.Tab>
    <Tabs.Tab value="b">Настройки</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel value="a">...</Tabs.Panel>
  <Tabs.Panel value="b">...</Tabs.Panel>
</Tabs>

Внутри Tabs хранит активную вкладку и отдаёт её детям через контекст. Пользователь сам решает порядок и разметку. Так устроены Radix, Headless UI, MUI Tabs.

Контролируемый и неконтролируемый компонент

Хороший переиспользуемый компонент умеет оба режима, как <input>:

function Tabs({ value, defaultValue, onValueChange, children }) {
  const [inner, setInner] = useState(defaultValue);
  const isControlled = value !== undefined;
  const active = isControlled ? value : inner;
  const select = (v) => {
    if (!isControlled) setInner(v);
    onValueChange?.(v);
  };
  // ...
}

Если передан value, правду хранит родитель; если нет, компонент хранит сам.

Headless-компоненты

Логика и доступность без стилей: хук или компонент отдаёт состояние и пропсы для элементов, разметку и стили пишете вы. Пример идеи: useCombobox возвращает getInputProps() и getItemProps().

Error boundary

Ловит ошибки рендера в поддереве и показывает запасной интерфейс. До сих пор пишется только классом (static getDerivedStateFromError, componentDidCatch), хука для этого в React нет. На практике берут пакет react-error-boundary. Не ловит ошибки в обработчиках событий и в асинхронном коде вне рендера.

Мелочи React 19, которые влияют на паттерны

  • ref теперь обычный проп у функциональных компонентов: function Input({ ref, ...props }). forwardRef больше не нужен.
  • Context рендерится как провайдер: <TabsContext value={...}>.
  • use(Context) можно вызывать в условии, в отличие от useContext.

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

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

Что спросят на собесе

  • Чем кастомный хук лучше HOC и render props?
  • Как бы ты спроектировал компонент Tabs или Select для дизайн-системы?
  • Что такое compound components и как они делят состояние?
  • Почему нельзя объявлять компонент внутри другого компонента?
  • Как поймать ошибку рендера и чего error boundary не ловит?

Закрепите за 5 минут

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