Перейти к содержимому
GitHub

Пользовательские элементы

Компоненты Svelte также могут быть скомпилированы в пользовательские элементы (также известные как веб-компоненты) с использованием опции компилятора customElement: true. Вы должны указать имя тега для компонента, используя элемент <svelte:options>.

<svelte:options customElement="my-element" />

<script>
  let { name = 'мир' } = $props();
</script>

<h1>Привет, {name}!</h1>
<slot />

Вы можете опустить имя тега для любых внутренних компонентов, которые не хотите раскрывать, и использовать их как обычные компоненты Svelte. Потребители компонента всё равно могут задать ему имя позже, если это необходимо, используя статическое свойство element, которое содержит конструктор пользовательского элемента и доступно, когда опция компилятора customElement установлена в true.

import MyElement from './MyElement.svelte';

customElements.define('my-element', MyElement.element);

После того как пользовательский элемент был определен, его можно использовать как обычный элемент DOM:

document.body.innerHTML = `
  <my-element>
    <p>Это контент со слотами</p>
  </my-element>
`;

Любые свойства становятся доступными как свойства элемента DOM (а также могут быть читаемыми/записываемыми как атрибуты, где это возможно).

const el = document.querySelector('my-element');

// получаем текущее значение свойства 'name'
console.log(el.name);

// задаём новое значение, обновляя shadow DOM
el.name = 'everybody';

Обратите внимание, что вам нужно явно перечислить все свойства. То есть, если вы сделаете let props = $props() без объявления props в параметрах компонента, Svelte не сможет определить, какие свойства следует выставить как свойства элемента DOM.

Жизненный цикл компонента

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

Когда создается пользовательский элемент, обёрнутый компонент Svelte не создается сразу. Он создается только на следующем такте после вызова connectedCallback. Свойства, присвоенные пользовательскому элементу до его вставки в DOM, временно сохраняются и затем устанавливаются при создании компонента, так что их значения не теряются. Однако это не работает для вызова экспортированных функций на пользовательском элементе, они становятся доступными только после монтирования элемента. Если вам нужно вызвать функции до создания компонента, вы можете обойти это, используя параметр extend.

Когда пользовательский элемент, написанный с помощью Svelte, создается или обновляется, Shadow DOM будет отражать значение на следующем такте, а не немедленно. Таким образом, обновления могут быть сгруппированы, и перемещения DOM, которые временно (но синхронно) отсоединяют элемент от DOM, не приводят к размонтированию внутреннего компонента.

Внутренний компонент Svelte уничтожается на следующем такте после вызова disconnectedCallback.

Параметры компонента

При создании пользовательского элемента вы можете настроить несколько аспектов, определив customElement как объект внутри <svelte:options>, начиная с Svelte 4. Этот объект может содержать следующие свойства:

  • tag: string: необязательное свойство tag для имени пользовательского элемента. Если оно установлено, пользовательский элемент с этим именем тега будет определен в реестре customElements при импорте этого компонента.
  • shadow: необязательное свойство, которое можно установить в "none", чтобы избежать создания корневого элемента Shadow DOM. Обратите внимание, что в этом случае стили больше не будут инкапсулированы, и вы не сможете использовать слоты.
  • props: необязательное свойство для изменения определённых деталей и поведения свойств вашего компонента. Оно предлагает следующие настройки:
    • attribute: string: Чтобы обновить свойство пользовательского элемента, у вас есть два варианта: либо установить свойство на ссылке пользовательского элемента, как показано выше, либо использовать HTML-атрибут. Для последнего имя атрибута по умолчанию — это имя свойства в нижнем регистре. Измените это, присвоив attribute: "<желаемое имя>".
    • reflect: boolean: По умолчанию обновлённые значения свойств не отражаются обратно в DOM. Чтобы включить это поведение, установите reflect: true.
    • type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object': При преобразовании значения атрибута в значение свойства и его отражении обратно, значение свойства по умолчанию считается String. Это может быть не всегда точно. Например, для типа числа определите его с помощью type: "Number". Вам не нужно перечислять все свойства, те, что не перечислены, будут использовать настройки по умолчанию.
  • extend: необязательное свойство, которое ожидает функцию в качестве аргумента. Ей передается класс пользовательского элемента, сгенерированный Svelte, и ожидается, что вы вернете класс пользовательского элемента. Это полезно, если у вас есть очень специфические требования к жизненному циклу пользовательского элемента или вы хотите улучшить класс, чтобы, например, использовать ElementInternals для лучшей интеграции с HTML-формами.
<svelte:options
  customElement={{
    tag: 'custom-element',
    shadow: 'none',
    props: {
      name: { reflect: true, type: 'Number', attribute: 'element-index' }
    },
    extend: (customElementConstructor) => {
      // Расширьте класс, чтобы он мог участвовать в HTML-формах
      return class extends customElementConstructor {
        static formAssociated = true;

        constructor() {
          super();
          this.attachedInternals = this.attachInternals();
        }

        // Добавьте функцию здесь, а не ниже в компоненте, чтобы
        // она была всегда доступна, а не только когда внутренний компонент Svelte
        // смонтирован
        randomIndex() {
          this.elementIndex = Math.random();
        }
      };
    }
  }}
/>

<script>
  let { elementIndex, attachedInternals } = $props();
  // ...
  function check() {
    attachedInternals.checkValidity();
  }
</script>

...

Предостережения и ограничения

Пользовательские элементы могут быть упакованы в компоненты для использования в приложении, не основанном на Svelte, так как они будут работать с обычным HTML и JavaScript, а также с большинством фреймворков. Однако есть несколько важных отличий, о которых следует знать:

  • Стили инкапсулированы, а не просто ограничены (если вы не установите shadow: "none"). Это означает, что любые стили, не относящиеся к компоненту (например, те, что могут быть в файле global.css), не будут применяться к пользовательскому элементу, включая стили с модификатором :global(...).
  • Вместо того чтобы извлекаться в отдельный .css файл, стили встраиваются в компонент как строка JavaScript.
  • Пользовательские элементы обычно не подходят для серверного рендеринга, так как Shadow DOM невидим до загрузки JavaScript.
  • В Svelte контент с слотами рендерится лениво. В DOM он рендерится жадно. Другими словами, он всегда будет создан, даже если элемент <slot> компонента находится внутри блока {#if ...}. Аналогично, включение <slot> в блоке {#each ...} не приведет к многократному рендерингу контента со слотами.
  • Устаревшая директива let: не имеет эффекта, потому что пользовательские элементы не имеют способа передавать данные родительскому компоненту, который заполняет слот.
  • Для поддержки старых браузеров требуются полифилы.
  • Вы можете использовать функцию контекста Svelte между обычными компонентами Svelte внутри пользовательского элемента, но не можете использовать их между пользовательскими элементами. Другими словами, вы не можете использовать setContext в родительском пользовательском элементе и считывать это с помощью getContext в дочернем пользовательском элементе.
  • Не объявляйте свойства или атрибуты, начинающиеся с on, так как их использование будет интерпретироваться как слушатель событий. Другими словами, Svelte рассматривает <custom-element oneworld={true}></custom-element> как customElement.addEventListener('eworld', true) (а не как customElement.oneworld = true).