Skip to main content

Що робить патерн "compound components" з контекстом?

Що таке "Compound Components"

Compound Components (складені компоненти) - це патерн, за якого кілька пов'язаних компонентів працюють разом, як єдиний "компонент-група".

Зазвичай у нас є "батько" та "дочірні елементи", які спілкуються між собою через React Context, замість передачі пропсів вручну.


Приклад (інтуїтивно)

Без патерна

javascript
<Tabs> <TabList> <Tab label="Опис" activeTab={activeTab} setActiveTab={setActiveTab} /> <Tab label="Відгуки" activeTab={activeTab} setActiveTab={setActiveTab} /> </TabList> <TabPanels activeTab={activeTab}> <TabPanel index={0}>Контент 1</TabPanel> <TabPanel index={1}>Контент 2</TabPanel> </TabPanels> </Tabs>

Мінуси:

  • доводиться прокидувати купу пропсів (activeTab, setActiveTab);
  • компоненти "жорстко зв'язані" - використовувати їх окремо незручно.

З патерном Compound Components

Мета:

  • Щоб Tabs, TabList, Tab, TabPanel самі знали про свій стан через внутрішній контекст;
  • щоб код виглядав природно:
javascript
<Tabs> <Tabs.List> <Tabs.Tab>Опис</Tabs.Tab> <Tabs.Tab>Відгуки</Tabs.Tab> </Tabs.List> <Tabs.Panels> <Tabs.Panel>Контент 1</Tabs.Panel> <Tabs.Panel>Контент 2</Tabs.Panel> </Tabs.Panels> </Tabs>

Як це працює

1. Створюємо контекст

javascript
const TabsContext = createContext(null);

2. Батьківський компонент (Provider)

javascript
function Tabs({ children }) { const [activeIndex, setActiveIndex] = useState(0); const value = useMemo(() => ({ activeIndex, setActiveIndex }), [activeIndex]); return ( <TabsContext.Provider value={value}> <div className="tabs">{children}</div> </TabsContext.Provider> ); }

3. Дочірні компоненти - підписники контексту

javascript
function TabList({ children }) { return <div className="tab-list">{children}</div>; } function Tab({ children, index }) { const { activeIndex, setActiveIndex } = useContext(TabsContext); const isActive = index === activeIndex; return ( <button className={isActive ? "active" : ""} onClick={() => setActiveIndex(index)} > {children} </button> ); } function Panels({ children }) { return <div className="tab-panels">{children}</div>; } function Panel({ children, index }) { const { activeIndex } = useContext(TabsContext); return activeIndex === index ? <div>{children}</div> : null; }

4. Збираємо "складений" компонент

javascript
Tabs.List = TabList; Tabs.Tab = Tab; Tabs.Panels = Panels; Tabs.Panel = Panel;

Що робить контекст тут

Контекст використовується як внутрішній механізм зв'язку між компонентами, щоб вони могли "знати" стан батька (Tabs), не потребуючи props на кожному рівні.

Тобто:

  • Tabs керує станом (активна вкладка);
  • Tabs.Tab і Tabs.Panel просто "підписані" на цей контекст;
  • усе синхронізується автоматично.

Переваги патерна

ПеревагаОпис
Чистий APIВкладений JSX відображає ієрархію логіки
Повторне використанняКомпоненти можна перевикористовувати незалежно
ІнкапсуляціяСтан Tabs прихований, доступ є тільки через контекст
ГнучкістьМожна вставляти власні компоненти між ними
Немає prop drillingЖодні пропси вручну не передаються вниз

Де це використовується

Цей патерн використовується повсюди:

  • у Headless UI, Radix UI, shadcn/ui, React Aria;
  • для Tabs, Accordion, Dropdown, Menu, Modal, Form, Stepper.

Підсумок

Compound Components + Context =

гнучкий інтерфейс, де дочірні компоненти автоматично знають, у якому контексті вони перебувають, і синхронізуються без пропсів.


Формула патерна:

  1. Створюєш контекст всередині "батька";
  2. Провайдиш значення через <Provider>;
  3. Дочірні компоненти дістають стан із контексту;
  4. Експортуєш дочірні компоненти як властивості головного.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.