SkateJS предоставляет мощный инструмент для построения веб-компонентов, поддерживающий реактивность, слоты и декларативное управление состоянием. Одной из продвинутых концепций является использование compound components, когда один основной компонент объединяет несколько взаимосвязанных компонентов, взаимодействующих друг с другом. Эта архитектура позволяет строить гибкие и расширяемые UI-элементы без потери инкапсуляции и контроля состояния.
Compound components позволяют разрабатывать группы компонентов, где:
В SkateJS для этого активно используются слоты, события и реактивные свойства, что делает структуру гибкой и декларативной.
Контейнерный компонент является центральным элементом, который
управляет состоянием всех дочерних компонентов. Рассмотрим пример с
компонентом <tab-group> для табов:
import { define, props, h } from 'skatejs';
class TabGroup extends HTMLElement {
static props = {
activeIndex: props.number
};
constructor() {
super();
this.activeIndex = 0;
}
connectedCallback() {
this.addEventListener('tab-selected', (event) => {
this.activeIndex = event.detail.index;
});
}
renderCallback() {
const children = Array.from(this.children);
return h('div', {}, children.map((child, index) =>
h('div', { class: index === this.activeIndex ? 'active' : 'inactive' }, child)
));
}
}
define('tab-group', TabGroup);
Ключевые моменты:
activeIndex хранит текущее активное состояние.tab-selected используется для уведомления контейнера о
действиях дочерних элементов.renderCallback динамически добавляет классы к дочерним
элементам на основе состояния.Дочерние компоненты (<tab-item>) не управляют
состоянием напрямую, а сообщают контейнеру о действиях через кастомные
события.
class TabItem extends HTMLElement {
static props = {
index: props.number
};
constructor() {
super();
this.index = 0;
}
connectedCallback() {
this.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('tab-selected', {
bubbles: true,
composed: true,
detail: { index: this.index }
}));
});
}
renderCallback() {
return h('button', {}, this.textContent);
}
}
define('tab-item', TabItem);
Особенности:
index связывает каждый элемент с позицией в
контейнере.tab-selected всплывает через дерево
DOM, позволяя контейнеру управлять состоянием.SkateJS поддерживает работу со слотами, что позволяет контейнеру и дочерним компонентам быть полностью независимыми в структуре DOM:
class TabGroupSlot extends HTMLElement {
static props = { activeIndex: props.number };
constructor() {
super();
this.activeIndex = 0;
}
connectedCallback() {
this.addEventListener('tab-selected', e => this.activeIndex = e.detail.index);
}
renderCallback() {
return h('div', {}, h('slot'));
}
}
define('tab-group-slot', TabGroupSlot);
Использование слота позволяет помещать <tab-item>
внутрь <tab-group-slot> без необходимости прямого
управления DOM:
<tab-group-slot>
<tab-item index="0">Первый</tab-item>
<tab-item index="1">Второй</tab-item>
<tab-item index="2">Третий</tab-item>
</tab-group-slot>
SkateJS автоматически отслеживает изменения свойств и перерисовывает компоненты. В compound components это важно, чтобы:
Например, изменение activeIndex в контейнере может
автоматически изменять визуальное состояние всех табов без ручного
обновления DOM.
Compound components легко расширяются:
Пример: добавление <tab-panel> для отображения
содержимого каждого таба:
class TabPanel extends HTMLElement {
static props = { index: props.number };
renderCallback() {
const group = this.closest('tab-group');
const active = group?.activeIndex === this.index;
return h('div', { style: { display: active ? 'block' : 'none' } }, this.textContent);
}
}
define('tab-panel', TabPanel);
Compound components в SkateJS представляют собой эффективный способ создания сложных UI-компонентов с четким разделением обязанностей и декларативной реактивностью. Правильная организация контейнера и дочерних компонентов позволяет создавать масштабируемые интерфейсы без лишнего связывания данных и ручного управления DOM.