Слоты и композиция

Haunted — это библиотека для создания веб-компонентов на базе функций и реактивного подхода в JavaScript. Одной из ключевых возможностей является поддержка слотов, что позволяет строить гибкие и композиционно богатые компоненты.

Слот — это место внутри компонента, куда можно передавать вложенный контент из родительского компонента. В Haunted используется стандартный механизм Web Components: <slot> и атрибуты name.

import { html, component } from 'haunted';

function Card({ title }) {
  return html`
    <div class="card">
      <h2>${title}</h2>
      <div class="content">
        <slot></slot>
      </div>
    </div>
  `;
}

customElements.define('my-card', component(Card));

В примере выше Card создаёт слот <slot></slot>. Любой контент, помещённый между тегами <my-card>, попадёт внутрь блока .content:

<my-card title="Пример">
  <p>Это текст внутри слота.</p>
</my-card>

Если требуется несколько слотов, можно использовать именованные слоты:

function Layout() {
  return html`
    <header><slot name="header"></slot></header>
    <main><slot></slot></main>
    <footer><slot name="footer"></slot></footer>
  `;
}

customElements.define('my-layout', component(Layout));
<my-layout>
  <div slot="header">Заголовок страницы</div>
  <p>Основное содержимое</p>
  <div slot="footer">Подвал сайта</div>
</my-layout>

Важно: слоты позволяют сохранять структуру и стили родителя, не нарушая реактивность Haunted. Компонент реагирует на изменения переданного контента, если этот контент тоже построен с Haunted или Web Components.


Передача свойств в слоты

Haunted напрямую не передаёт свойства родителя в слот, но можно использовать context или функции обратного вызова для взаимодействия с вложенным контентом. Это особенно полезно при создании интерактивных или динамических интерфейсов.

function ListItem({ onClick, text }) {
  return html`<li @click=${onClick}>${text}</li>`;
}

function List({ items, onItemClick }) {
  return html`
    <ul>
      ${items.map(item => html`<${ListItem} text=${item} .onCl ick=${() => onItemClick(item)}></${ListItem}>`)}
    </ul>
  `;
}

customElements.define('my-list', component(List));

В этом примере каждый элемент списка получает своё событие клика через свойство .onClick. Это демонстрирует композицию: компонент List управляет данными и поведением ListItem, передавая нужные функции.


Композиция компонентов

Haunted поддерживает функциональную композицию, что позволяет строить сложные интерфейсы из маленьких, переиспользуемых компонентов. Основная идея — компоненты могут встраивать друг друга, передавать слоты, свойства и события.

function Modal({ open, onClose }) {
  return html`
    ${open ? html`
      <div class="overlay" @click=${onClose}>
        <div class="modal" @click=${e => e.stopPropagation()}>
          <slot></slot>
        </div>
      </div>` : ''}
  `;
}

customElements.define('my-modal', component(Modal));

Использование:

<my-modal .open=${true} .onCl ose=${() => console.log('Закрыт')}>
  <p>Содержимое модального окна</p>
</my-modal>

Композиция здесь реализуется через слот <slot></slot> и управление состоянием родителя. Вложенный контент остаётся реактивным, а модальное окно остаётся полностью переиспользуемым.


Реактивность и слоты

Haunted строит интерфейс на реактивных свойствах и html шаблонах. Важное свойство слотов: содержимое слота может быть реактивным, если оно само использует Haunted шаблоны.

function Counter() {
  const [count, setCount] = useState(0);
  return html`
    <div>
      <button @click=${() => setCount(count - 1)}>-</button>
      <span>Счёт: ${count}</span>
      <button @click=${() => setCount(count + 1)}>+</button>
      <slot></slot>
    </div>
  `;
}

customElements.define('my-counter', component(Counter));

Слот здесь может содержать любой реактивный компонент, который будет обновляться независимо, сохраняя синхронизацию с родителем.


Стилизация слотов

Стили слотов могут быть контролируемыми через Shadow DOM или глобальными. Haunted использует стандарт Shadow DOM, поэтому стили из родителя не применяются к слоту напрямую, если не использовать ::slotted():

::slotted(p) {
  color: blue;
  font-weight: bold;
}

Это позволяет точно управлять внешним видом вложенного контента, не нарушая инкапсуляцию компонента.


Резюме принципов

  • Слоты — механизм передачи вложенного контента.
  • Именованные слоты позволяют распределять контент по различным областям компонента.
  • Композиция — создание сложных компонентов из маленьких, с передачей свойств и событий.
  • Реактивность сохраняется внутри слотов, если вложенный контент построен на Haunted.
  • Стилизация контролируется через Shadow DOM и ::slotted().

Использование слотов в Haunted обеспечивает гибкую и модульную архитектуру, позволяя создавать высоко-переиспользуемые, динамические интерфейсы с минимальной связностью между компонентами.