Що робить патерн "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 =
гнучкий інтерфейс, де дочірні компоненти автоматично знають, у якому контексті вони перебувають, і синхронізуються без пропсів.
Формула патерна:
- Створюєш контекст всередині "батька";
- Провайдиш значення через
<Provider>; - Дочірні компоненти дістають стан із контексту;
- Експортуєш дочірні компоненти як властивості головного.
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.