Compound Components представляют собой паттерн проектирования, широко применяемый в библиотеке Preact и других реактивных фреймворках. Основная идея заключается в разделении логики компонента и его визуальных частей, позволяя создавать гибкие и расширяемые интерфейсы, где отдельные подкомпоненты могут взаимодействовать с общим состоянием родителя.
Контейнер и дочерние элементы Родительский компонент выступает как контейнер, который управляет состоянием и поведением. Дочерние компоненты получают доступ к этому состоянию через контекст или специальные пропсы. Такой подход устраняет необходимость пробрасывания пропсов через несколько уровней вложенности (prop drilling).
Разделение ответственности Контейнер отвечает за бизнес-логику, состояние и управление событиями. Дочерние компоненты фокусируются на отображении данных. Это делает код более читаемым и поддерживаемым.
Гибкость и расширяемость Паттерн позволяет добавлять новые дочерние компоненты без изменения внутренней логики родителя. Каждый дочерний элемент может быть полностью настраиваемым, что упрощает повторное использование.
Рассмотрим создание компонента Tabs, где есть несколько
вкладок и переключение между ними.
import { h } from 'preact';
import { useState, createContext, useContext } from 'preact/hooks';
const TabsContext = createContext();
function Tabs({ children, defaultIndex = 0 }) {
const [activeIndex, setActiveIndex] = useState(defaultIndex);
return (
<TabsContext.Provider value={{ activeIndex, setActiveIndex }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function TabList({ children }) {
return <div className="tab-list">{children}</div>;
}
function Tab({ index, children }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
const isActive = activeIndex === index;
return (
<button
className={`tab ${isActive ? 'active' : ''}`}
onCl ick={() => setActiveIndex(index)}
>
{children}
</button>
);
}
function TabPanels({ children }) {
const { activeIndex } = useContext(TabsContext);
return <div className="tab-panels">{children[activeIndex]}</div>;
}
export { Tabs, TabList, Tab, TabPanels };
В этом примере:
Tabs — контейнер, управляющий состоянием активной
вкладки.TabList — визуальный обертка для кнопок вкладок.Tab — отдельная вкладка, которая получает индекс и
проверяет активна ли она.TabPanels — отображает содержимое только активной
вкладки.Использование createContext и useContext
позволяет дочерним компонентам автоматически получать доступ к состоянию
родителя без передачи пропсов через каждый уровень. Это особенно важно
при создании сложных интерфейсов, где количество вложенных компонентов
может быть большим.
const TabsContext = createContext();
Контекст содержит:
activeIndex — текущий выбранный индекс вкладки.setActiveIndex — функция для изменения активной
вкладки.Compound Components позволяют создавать более сложные сценарии, такие как:
Применение этого паттерна упрощает управление состоянием, делает интерфейсы модульными и облегчает сопровождение больших приложений на Preact.
Этот подход обеспечивает чистую архитектуру, повышает гибкость компонентов и сокращает количество повторяющегося кода.