Render Props через слоты

Техника render props через слоты в библиотеке Haunted позволяет динамически передавать разметку и логику дочерним компонентам, сохраняя контроль за структурой и поведением на уровне родителя. Это делает композицию компонентов более гибкой и естественной для веб-компонентов.

Render props адаптируются к веб-компонентам через HTML-слоты. Haunted не модифицирует специфику слотов, а лишь облегчает управление состоянием и реактивностью. В результате слоты становятся аналогом функций-рендеров, характерных для React-подхода.

Ключевой момент: слот отвечает за передачу части разметки, а Haunted-компонент — за реактивное состояние и синхронизацию данных.

Объявление компонента-обёртки со слотами

Компонент объявляется как классический Haunted-компонент на основе component и функции-хука. Внутри шаблона используется <slot> для внедрения произвольного контента.

import { component, useState, html } from 'haunted';

function Panel() {
  const [open, setOpen] = useState(true);

  return html`
    <section class="${open ? 'open' : 'closed'}">
      <header>
        <button @click=${() => setOpen(o => !o)}>
          ${open ? 'Скрыть' : 'Показать'}
        </button>
      </header>
      <div class="content">
        <slot></slot>
      </div>
    </section>
  `;
}

customElements.define('x-panel', component(Panel));

Слот в данном случае выступает как render prop: родительский компонент задаёт логику, а внешний код передаёт разметку для отображения.

Сложные структуры через именованные слоты

Для более детальной передачи частей разметки используются именованные слоты. Они позволяют распределять контент по зонам интерфейса и усиливают аналогию с render props, передающими несколько функций.

function Card() {
  return html`
    <article class="card">
      <header><slot name="title"></slot></header>
      <section><slot name="body"></slot></section>
      <footer><slot name="actions"></slot></footer>
    </article>
  `;
}

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

Важный аспект: Haunted при этом не внедряет собственные протоколы передачи данных; слоты соответствуют стандарту Custom Elements и работают независимо от фреймворка.

Сопровождение стейтом и реактивностью

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

function Tabs() {
  const [active, setActive] = useState('one');

  return html`
    <nav>
      <button @click=${() => setActive('one')}>Первая</button>
      <button @click=${() => setActive('two')}>Вторая</button>
    </nav>
    <section>
      ${active === 'one'
        ? html`<slot name="one"></slot>`
        : html`<slot name="two"></slot>`}
    </section>
  `;
}

customElements.define('x-tabs', component(Tabs));

Слоты в данном примере эмулируют динамический выбор рендера, что полностью соответствует render props-паттерну.

Передача данных внутрь слотов

Стандартные слоты не позволяют напрямую передавать параметры внутрь контента. Для передачи данных используется механизм событий либо отражение атрибутов на уровне веб-компонента. Render props через слоты подразумевают передачу не только структуры, но и контекста, поэтому практикуется следующий подход:

1. Пробрасывание состояния через атрибуты

Компонент обновляет атрибуты, а вложенная разметка реагирует через CSS или JavaScript.

2. Использование кастомных событий

Компонент диспатчит событие, а внешний код принимает и использует его для рендера. В этом случае слот выступает как канал рендера, а событие — как канал данных.

Инкапсуляция стилей и отношения render props

Shadow DOM влияет на применимость стилей, что является особенностью render props через слоты в веб-компонентах. Содержимое слота рендерится вне shadow root, а значит наследует окружение родительского документа. Такой механизм выгоден для render props, поскольку контент может быть стилизован независимо от внутренней структуры компонента.

Ключевое следствие: компонент управляет структурой и логикой, а внешний контент управляет стилями и визуальным слоем.

Композиция компонентов и повторное использование

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

Композиция особенно выразительна при построении UI-библиотек на веб-компонентах. Haunted обеспечивает минимальный уровень абстракции, позволяя сосредоточиться на React-подобной реактивности без существенного усложнения.

Сравнение со стандартными render props

В подходе React render props — это функции, возвращающие JSX. В веб-компонентах те же идеи реализуются через слоты и Shadow DOM. Haunted дополняет архитектуру реактивностью и хуками, облегчая создание компонентов со сложной внутренней логикой без отказа от нативных механизмов браузера.

Сходство:

  • гибкость композиции;
  • управление отображением через родительский компонент;
  • независимость структуры и внешнего оформления.

Отличие:

  • отсутствие непосредственной передачи функций внутрь слота;
  • использование событий и атрибутов вместо параметров функции;
  • разнесение стилей между Shadow DOM и документом.

Практическое применение в UI-паттернах

Render props через слоты применяются в компонентах:

  • панелей и аккордеонов;
  • навигационных табов и переключателей;
  • формовых контейнеров;
  • карточек и всплывающих панелей.

Этот подход особенно ценен, когда структура компонента фиксирована, а контент может варьироваться.

Итоги техники

Render props через слоты в Haunted — способ привнести композиционные практики современных фреймворков в мир веб-компонентов, используя нативные стандарты HTML. Haunted не нарушает семантики слотов и не изменяет модель DOM, что обеспечивает совместимость, предсказуемость и расширяемость.