Составные компоненты

  —  3 минуты

#web#javascript#typescript#theory#code#react#patterns
Читать статью в Telegram

Есть такой паттерн для реакта, который называется Compound Components. Это можно перевести как "составные компоненты"

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

То есть мы можем заранее объединить компоненты каким-то контекстом и переиспользовать их, например, через общее пространство имён, в качестве которого чаще всего выступает родительский компонент

Запутались в словах? Лучше посмотреть в коде:

Вот пример прям из доки Ant Design:

javascript
1import { Layout } from 'antd';
2
3<Layout>
4    <Layout.Header>Header</Layout.Header>
5    <Layout.Content>Content</Layout.Content>
6    <Layout.Footer>Footer</Layout.Footer>
7</Layout>
8

Обратили внимание, как компоненты Header, Content и Footer мы получаем напрямую из компонента Layout? Это и есть пример паттерна Compound Components. Компоненты связаны, а используются они из общего пространства — компонента Layout

Зачем же так сделали? Тут преследуется три цели:

  1. Явно показать на уровне нейминга, что использовать Layout.Footer вне Layout не нужно
  2. Расшарить общий контекст между всеми компонентами Layout
  3. Корректно стилизовать части Layout в зависимости от значения внутри общего контекста

С неймингом и стилями, думаю, всё предельно ясно. Но что насчёт контекста? На самом деле, Layout под собой содержит ещё и LayoutContext, который содержит в себе состояние компонента Sider и распространяет его на все дочерние компоненты. Схематически это выглядит примерно так:

text
1InternalLayout  
2└ LayoutContext <-- инициализируем контекст
3    ├ Header
4    ├ Content <-- а в этих компонентах получаем его значение
5    ├ Footer
6    └ Sider
7

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

С точки зрения типов всё тоже не сложно. Тот же Ant Design делает так:

typescript
1import InternalLayout, { Content, Footer, Header } from './layout';
2import Sider from './Sider';
3
4// получаем тип базового layout компонента
5type InternalLayoutType = typeof InternalLayout;
6
7// создаём тип, который определит какие компоненты мы вложим в layout
8type CompoundedComponent = InternalLayoutType & {
9    Header: typeof Header;
10    Footer: typeof Footer;
11    Content: typeof Content;
12    Sider: typeof Sider;
13};
14
15// нагло переприсваиваем тип
16const Layout = InternalLayout as CompoundedComponent;
17
18// нагло биндим нужные компоненты
19Layout.Header = Header;
20Layout.Footer = Footer;
21Layout.Content = Content;
22Layout.Sider = Sider;
23
24// не менее нагло экспортируем как public-api
25export default Layout;
26

Статья была полезной?