Концепция слотов

Слоты в Stencil — это механизмы, которые позволяют внедрять контент в компоненты. Подобно слоту в шаблонизаторах или других фреймворках, Stencil использует их для динамической вставки пользовательского контента в заранее определённые места компонента. Это мощный инструмент для создания повторно используемых и гибких компонентов, позволяющий разработчику контролировать, где и как будет отображаться пользовательский контент, не изменяя структуру компонента.

Что такое слот?

Слот в Stencil — это область в компоненте, которая может быть заполнена внешним содержимым. Вся суть концепции слотов заключается в том, что компонент заранее определяет места, в которые пользователь может вставить свои данные. При этом сам компонент остаётся независимым и универсальным, а контент, переданный в слот, может быть обработан в зависимости от контекста использования.

Слоты в Stencil реализуются с помощью элемента <slot>, который вставляется в разметку компонента. Этот элемент указывает место, где будет рендериться контент, переданный компоненту при его использовании.

Основное использование слотов

Самый простой пример использования слота выглядит следующим образом:

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true
})
export class MyComponent {
  render() {
    return (
      <div>
        <h1>Заголовок компонента</h1>
        <slot></slot>
      </div>
    );
  }
}

В этом примере компонент my-component содержит слот. Когда компонент будет использован в другом месте приложения, в слот можно передать дополнительный контент:

<my-component>
  <p>Это пользовательский контент, который будет вставлен в слот.</p>
</my-component>

При рендере компонента контент, переданный в слот, будет отображён там, где находится <slot></slot> в шаблоне компонента.

Слоты с аттрибутами

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

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true
})
export class MyComponent {
  render() {
    return (
      <div>
        <h1>Заголовок компонента</h1>
        <slot name="header"></slot>
        <slot></slot>
        <slot name="footer"></slot>
      </div>
    );
  }
}

Теперь при использовании компонента можно указать, в какой слот передаётся контент:

<my-component>
  <div slot="header">Заголовок, вставленный в слот</div>
  <p>Это основной контент компонента.</p>
  <div slot="footer">Футер, вставленный в слот</div>
</my-component>

В данном случае контент, переданный в слот с аттрибутом name="header", будет отображён в месте, где находится <slot name="header"></slot>, а контент в слот по умолчанию будет вставлен в первый слот.

Слоты с дефолтным контентом

Если слот не имеет переданного контента, можно указать дефолтное содержимое, которое будет использоваться, если в слот ничего не было передано. Это полезно для задания fallback-контента.

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true
})
export class MyComponent {
  render() {
    return (
      <div>
        <h1>Заголовок компонента</h1>
        <slot>
          <p>Дефолтный контент, если ничего не передано в слот.</p>
        </slot>
      </div>
    );
  }
}

Если компонент будет использован без передачи контента в слот, то вместо пустого места появится указанный дефолтный контент. В случае передачи контента, он заменит дефолтный.

Доступ к слотам в JavaScript

Stencil предоставляет возможность работать с слотами через API, что позволяет получать доступ к содержимому слота и манипулировать им на уровне JavaScript.

Например, можно использовать метод slot.assignedNodes(), чтобы получить все элементы, переданные в слот:

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true
})
export class MyComponent {
  @Element() el: HTMLElement;

  componentDidLoad() {
    const slot = this.el.shadowRoot.querySelector('slot');
    const assignedNodes = slot.assignedNodes();
    console.log(assignedNodes);
  }

  render() {
    return (
      <div>
        <h1>Заголовок компонента</h1>
        <slot></slot>
      </div>
    );
  }
}

Этот код позволит вывести все узлы, переданные в слот, после того как компонент будет загружен.

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

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

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true
})
export class MyComponent {
  @State() content: string = 'Пользовательский контент';

  render() {
    return (
      <div>
        <h1>Заголовок компонента</h1>
        <slot>{this.content}</slot>
      </div>
    );
  }
}

Здесь содержимое слота зависит от состояния компонента, и при изменении этого состояния содержимое, переданное в слот, будет автоматически обновляться.

Заключение

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