Compound components — это архитектурный подход, при котором один компонент выступает контейнером для нескольких вложенных компонентов, обеспечивая согласованное взаимодействие между ними. Такой подход позволяет создавать гибкие, переиспользуемые интерфейсы с понятной структурой.
В Chakra UI этот паттерн реализуется через сочетание контекста React, пропсов children и forwardRef, что позволяет вложенным компонентам получать данные от родителя без явной передачи пропсов на каждом уровне.
Родительский компонент выступает центральным узлом, который управляет состоянием и логикой. Для передачи данных вложенным компонентам используется контекст:
import { createContext, useContext, useState } from "react";
import { Box, Button } from "@chakra-ui/react";
const AccordionContext = createContext();
function Accordion({ children }) {
const [openIndex, setOpenIndex] = useState(null);
const toggleItem = (index) => {
setOpenIndex(openIndex === index ? null : index);
};
return (
<AccordionContext.Provider value={{ openIndex, toggleItem }}>
<Box>{children}</Box>
</AccordionContext.Provider>
);
}
openIndex и
toggleItem.Вложенные компоненты используют контекст для взаимодействия с родителем:
function AccordionItem({ index, children }) {
const { openIndex, toggleItem } = useContext(AccordionContext);
const isOpen = openIndex === index;
return (
<Box border="1px solid" borderColor="gray.200" borderRadius="md" mb={2}>
{children({ isOpen, toggleItem: () => toggleItem(index) })}
</Box>
);
}
isOpen и метод
toggleItem.AccordionItem иметь доступ к состоянию
открытия/закрытия.Использование Compound Components часто подразумевает разделение на Header и Panel:
function AccordionHeader({ children, onClick }) {
return (
<Button width="100%" textAlign="left" onCl ick={onClick}>
{children}
</Button>
);
}
function AccordionPanel({ isOpen, children }) {
return isOpen ? <Box p={4}>{children}</Box> : null;
}
AccordionHeader реагирует на клики и вызывает
toggleItem.AccordionPanel отображается только если
isOpen равен true.<Accordion>
<AccordionItem index={0}>
{({ isOpen, toggleItem }) => (
<>
<AccordionHeader onCl ick={toggleItem}>Пункт 1</AccordionHeader>
<AccordionPanel isOpen={isOpen}>Содержимое 1</AccordionPanel>
</>
)}
</AccordionItem>
<AccordionItem index={1}>
{({ isOpen, toggleItem }) => (
<>
<AccordionHeader onCl ick={toggleItem}>Пункт 2</AccordionHeader>
<AccordionPanel isOpen={isOpen}>Содержимое 2</AccordionPanel>
</>
)}
</AccordionItem>
</Accordion>
AccordionItem управляет своим состоянием через
общий контекст.Compound Components позволяют легко добавлять новые вложенные элементы без изменения родителя. Например, добавим иконку состояния:
import { ChevronDownIcon, ChevronUpIcon } from "@chakra-ui/icons";
function AccordionHeader({ children, onClick, isOpen }) {
return (
<Button width="100%" textAlign="left" onCl ick={onClick} rightIcon={isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}>
{children}
</Button>
);
}
isOpen.AccordionFooter или AccordionBadge, просто
подключив их к контексту.Для интеграции с Chakra UI и внешними библиотеками удобно использовать forwardRef:
import { forwardRef } from "react";
const AccordionHeader = forwardRef(({ children, onClick, isOpen }, ref) => (
<Button ref={ref} width="100%" textAlign="left" onCl ick={onClick} rightIcon={isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}>
{children}
</Button>
));
В Chakra UI паттерн Compound Components позволяет создавать сложные UI-структуры с чистой архитектурой и минимальной связностью. Контекст React обеспечивает передачу состояния между компонентами, а использование функций-children и forwardRef делает компоненты максимально гибкими и совместимыми с Chakra UI стилями и хуками. Такой подход идеально подходит для создания аккордеонов, табов, модальных окон и любых компонентов с вложенной структурой и общим управлением состоянием.