Compound компоненты в Atomico представляют собой способ объединения нескольких логически связанных элементов в единый компонент, сохраняя при этом высокую степень переиспользуемости и управляемости состояния. Это особенно важно при построении сложных интерфейсов, где один визуальный элемент может состоять из множества вложенных подкомпонентов с разными обязанностями.
Основная идея заключается в том, чтобы разделять
логику и визуальную структуру
компонентов. В Atomico это реализуется через использование функции
component, реактивных состояний и возможности передачи
дочерних компонентов через слоты.
Простейший пример:
import { component, useState, html } from "atomico";
function Tabs({ children }) {
const [activeIndex, setActiveIndex] = useState(0);
return html`
<div class="tabs">
<div class="tabs-header">
${children.map((child, index) => html`
<button
class="${index === activeIndex ? "active" : ""}"
oncl ick=${() => setActiveIndex(index)}
>
${child.props.label}
</button>
`)}
</div>
<div class="tabs-content">
${children[activeIndex]}
</div>
</div>
`;
}
Tabs.props = {
children: Array
};
export const tabs = component(Tabs);
В этом примере компонент Tabs управляет состоянием
активного таба и рендерит только активный дочерний компонент.
Compound компоненты часто используют дочерние
компоненты, чтобы структура оставалась гибкой. Для передачи
дочерних элементов применяется стандартный JSX-подобный синтаксис через
слоты или children.
import { component, html } from "atomico";
import { tabs } from "./Tabs";
function Tab({ children }) {
return html`<div class="tab-panel">${children}</div>`;
}
Tab.props = {
label: String,
children: String
};
export const tab = component(Tab);
Теперь Tabs и Tab могут быть
объединены:
html`
<tabs>
<tab label="Первый">Содержимое первого таба</tab>
<tab label="Второй">Содержимое второго таба</tab>
<tab label="Третий">Содержимое третьего таба</tab>
</tabs>
`;
Ключевой момент: Tabs не зависит от
конкретной реализации Tab. Любой компонент, соответствующий
интерфейсу дочернего элемента (наличие label и
children), может быть использован.
Atomico предлагает реактивные состояния через useState и
useEffect. В compound компонентах важно синхронизировать
состояние между контейнером и дочерними элементами.
Пример расширенного Tabs с внешним контролем:
function ControlledTabs({ children, value, onChange }) {
return html`
<div class="tabs">
<div class="tabs-header">
${children.map((child, index) => html`
<button
class="${index === value ? "active" : ""}"
oncl ick=${() => onChange(index)}
>
${child.props.label}
</button>
`)}
</div>
<div class="tabs-content">
${children[value]}
</div>
</div>
`;
}
ControlledTabs.props = {
children: Array,
value: Number,
onChange: Function
};
export const controlledTabs = component(ControlledTabs);
Такой подход позволяет использовать compound компонент как контролируемый компонент, где состояние управляется извне.
Для расширения возможностей часто используется механизм передачи
дополнительных пропсов через контекст или специальные атрибуты. В
Atomico это реализуется через Context:
import { createContext } from "atomico";
const TabsContext = createContext();
function TabsProvider({ children, activeIndex, setActiveIndex }) {
return html`
<${TabsContext.Provider} value=${{ activeIndex, setActiveIndex }}>
${children}
</${TabsContext.Provider}>
`;
}
Дочерние компоненты могут получать доступ к состоянию:
import { useContext } from "atomico";
import { TabsContext } from "./TabsContext";
function TabPanel({ children, index }) {
const { activeIndex } = useContext(TabsContext);
return html`${activeIndex === index ? children : null}`;
}
Такой подход полностью отделяет логику управления состоянием от визуальных компонентов.
Atomico позволяет использовать scoped CSS через объект
style внутри функции компонента или через внешние
CSS-файлы. При построении compound компонентов рекомендуется:
Пример:
function Tabs({ children }) {
return html`
<style>
.tabs-header { display: flex; gap: 8px; }
.tabs-content { padding: 16px; border: 1px solid #ccc; }
.active { font-weight: bold; }
</style>
<div class="tabs">
<div class="tabs-header">
${children.map((child, index) => html`<button>${child.props.label}</button>`)}
</div>
<div class="tabs-content">${children[0]}</div>
</div>
`;
}
useState и useEffect, возможность
контролировать состояние извне.Compound компоненты в Atomico создают гибкую архитектуру интерфейса, обеспечивая чистое разделение обязанностей и удобное управление состоянием сложных UI-модулей.