Spec-Zone.ru › Lit 2

Работа с теневым DOM

Компоненты Lit используют теневой DOM для инкапсуляции своего DOM. Теневой DOM позволяет добавить к элементу отдельное изолированное и инкапсулированное дерево DOM. Инкапсуляция DOM — ключ к обеспечению совместимости с любым другим кодом, включая другие веб-компоненты или компоненты Lit, работающие на странице.

Теневой DOM предоставляет три преимущества:

  • Область видимости DOM. API DOM, такие как document.querySelector, не находят элементы в теневом DOM компонента, поэтому глобальным скриптам сложнее случайно нарушить работу компонента.
  • Область видимости стилей. Вы можете создавать инкапсулированные стили для теневого DOM, не затрагивающие остальную часть дерева DOM.
  • Композиция. Теневой корень компонента, содержащий его внутренний DOM, отделён от дочерних элементов компонента. Вы можете выбрать, как дочерние элементы будут отображаться во внутреннем DOM компонента.

Дополнительную информацию о теневом DOM можно найти здесь:

  • Теневой DOM v1: автономные веб-компоненты на сайте Web Fundamentals.
  • Использование теневого DOM на MDN.

Старые браузеры. В старых браузерах, где нативный теневой DOM недоступен, можно использовать полифилы веб-компонентов. Обратите внимание, что модуль polyfill-support Lit необходимо загружать вместе с полифилами веб-компонентов. Подробности см. в разделе Требования для старых браузеров.

Доступ к узлам в теневом DOM

Lit отображает компоненты в свой renderRoot, который по умолчанию является теневым корнем. Чтобы найти внутренние элементы, можно использовать API запросов DOM, например this.renderRoot.querySelector().

renderRoot всегда должен быть теневым корнем или элементом, поскольку у них есть общие API, такие как .querySelectorAll() и .children.

Можно запрашивать внутренний DOM после первоначального рендеринга компонента (например, в firstUpdated) или использовать шаблон с геттером:

firstUpdated() {
  this.staticNode = this.renderRoot.querySelector('#static-node');
}

get _closeButton() {
  return this.renderRoot.querySelector('#close-button');
}

LitElement предоставляет набор декораторов, позволяющих кратко определять подобные геттеры.

Декораторы @query, @queryAll и @queryAsync

Декораторы @query, @queryAll и @queryAsync позволяют удобно получать доступ к узлам внутреннего DOM компонента.

Использование декораторов. Декораторы — это предлагаемая возможность JavaScript, поэтому для их использования понадобится компилятор, например Babel или TypeScript. Подробности см. в разделе Использование декораторов.

@query

Изменяет свойство класса, превращая его в геттер, возвращающий узел из корня рендеринга. Необязательный второй аргумент, если он равен true, выполняет запрос DOM только один раз и кэширует результат. Это можно использовать для оптимизации производительности в случаях, когда запрашиваемый узел не будет меняться.

import {LitElement, html} from 'lit';
import {query} from 'lit/decorators/query.js';

class MyElement extends LitElement {
  @query('#first')
  _first;

  render() {
    return html`
      <div id="first"></div>
      <div id="second"></div>
    `;
  }
}

Этот декоратор эквивалентен следующему коду:

get _first() {
  return this.renderRoot?.querySelector('#first') ?? null;
}

@queryAll

Работает так же, как query, но возвращает все подходящие узлы, а не один. Эквивалентен вызову querySelectorAll.

import {LitElement, html} from 'lit';
import {queryAll} from 'lit/decorators/queryAll.js';

class MyElement extends LitElement {
  @queryAll('div')
  _divs;

  render() {
    return html`
      <div id="first"></div>
      <div id="second"></div>
    `;
  }
}

В этом примере _divs вернёт оба элемента <div> в шаблоне. В TypeScript тип свойства @queryAll — это NodeListOf<HTMLElement>. Если вы точно знаете, какие именно узлы будут получены, тип можно указать точнее:

@queryAll('button')
_buttons!: NodeListOf<HTMLButtonElement>

Восклицательный знак (!) после buttons — это оператор утверждения ненулевого значения TypeScript. Он указывает компилятору считать, что buttons всегда определён и никогда не равен null или undefined.

@queryAsync

Работает аналогично @query, но вместо непосредственного возврата узла возвращает Promise, который разрешается в этот узел после завершения всех ожидающих рендерингов элемента. Вместо ожидания промиса updateComplete можно использовать этот способ.

Это полезно, например, если узел, возвращаемый @queryAsync, может измениться в результате изменения другого свойства.

Отображение дочерних элементов с помощью слотов

Ваш компонент может принимать дочерние элементы (например, элемент <ul> может иметь дочерние элементы <li>).

<my-element>
  <p>A child</p>
</my-element>

По умолчанию дочерние элементы элемента с теневым деревом вообще не отображаются.

Чтобы отображать дочерние элементы, шаблон должен содержать один или несколько элементов <slot>, которые служат заполнителями для дочерних узлов.

Использование элемента slot

Чтобы отображать дочерние элементы элемента, создайте для них <slot> в шаблоне элемента. Дочерние элементы не перемещаются в дереве DOM, но отображаются так, как если бы они были дочерними элементами <slot>. Например:

Использование именованных слотов

Чтобы назначить дочерний элемент определённому слоту, убедитесь, что атрибут slot дочернего элемента совпадает с атрибутом name слота:

  • Именованные слоты принимают только дочерние элементы с соответствующим атрибутом slot.

    Например, <slot name="one"></slot> принимает только дочерние элементы с атрибутом slot="one".

  • Дочерние элементы с атрибутом slot отображаются только в слоте с соответствующим атрибутом name.

    Например, <p slot="one">...</p> будет помещён только в <slot name="one"></slot>.

Задание резервного содержимого слота

Для слота можно задать резервное содержимое. Оно отображается, если слоту не назначен ни один дочерний элемент.

<slot>I am fallback content</slot>

Отображение резервного содержимого. Если слоту назначены какие-либо дочерние узлы, его резервное содержимое не отображается. Слот по умолчанию без имени принимает любые дочерние узлы. Он не будет отображать резервное содержимое, даже если единственные назначенные ему узлы — это текстовые узлы, содержащие пробелы, например <example-element> </example-element>. При использовании выражения Lit в качестве дочернего элемента пользовательского элемента при необходимости используйте неотображаемое значение, чтобы отобразилось резервное содержимое слота. Дополнительную информацию см. в разделе Удаление дочернего содержимого.

Доступ к дочерним элементам, назначенным слотам

Для доступа к дочерним элементам, назначенным слотам в теневом корне, можно использовать стандартные методы slot.assignedNodes или slot.assignedElements вместе с событием slotchange.

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

get _slottedChildren() {
  const slot = this.shadowRoot.querySelector('slot');
  return slot.assignedElements({flatten: true});
}

Также можно использовать событие slotchange, чтобы реагировать на изменение назначенных узлов. В следующем примере извлекается текстовое содержимое всех дочерних элементов, назначенных слотам.

handleSlotchange(e) {
  const childNodes = e.target.assignedNodes({flatten: true});
  // ... do something with childNodes ...
  this.allText = childNodes.map((node) => {
    return node.textContent ? node.textContent : ''
  }).join('');
}

render() {
  return html`<slot @slotchange=${this.handleSlotchange}></slot>`;
}

Дополнительную информацию см. в разделе HTMLSlotElement на MDN.

Декораторы @queryAssignedElements и @queryAssignedNodes

Декораторы @queryAssignedElements и @queryAssignedNodes преобразуют свойство класса в геттер, который возвращает результат вызова slot.assignedElements или slot.assignedNodes соответственно для заданного слота в теневом дереве компонента. Используйте их, чтобы получить элементы или узлы, назначенные указанному слоту.

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

Свойство Описание
flatten Логическое значение, указывающее, нужно ли уплощать назначенные узлы, заменяя любые дочерние элементы <slot> назначенными им узлами.
slot Имя слота, для которого выполняется запрос. Не указывайте значение, чтобы выбрать слот по умолчанию.
selector (только для queryAssignedElements) Если указано, возвращаются только назначенные элементы, соответствующие этому селектору CSS.

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

Использование декораторов. Декораторы — это предлагаемая возможность JavaScript, поэтому для их использования понадобится компилятор, например Babel или TypeScript. Подробности см. в разделе Использование декораторов.

@queryAssignedElements({slot: 'list', selector: '.item'})
_listItems!: Array<HTMLElement>;

@queryAssignedNodes({slot: 'header', flatten: true})
_headerNodes!: Array<Node>;

Приведённые выше примеры эквивалентны следующему коду:

get _listItems() {
  const slot = this.shadowRoot.querySelector('slot[name=list]');
  return slot.assignedElements().filter((node) => node.matches('.item'));
}

get _headerNodes() {
  const slot = this.shadowRoot.querySelector('slot[name=header]');
  return slot.assignedNodes({flatten: true});
}

Настройка корня рендеринга

У каждого компонента Lit есть корень рендеринга — узел DOM, служащий контейнером для его внутреннего DOM.

По умолчанию LitElement создаёт открытый shadowRoot и выполняет рендеринг внутри него, формируя следующую структуру DOM:

<my-element>
  #shadow-root
    <p>child 1</p>
    <p>child 2</p>

Есть два способа настроить корень рендеринга, используемый LitElement:

  • Задать shadowRootOptions.
  • Реализовать метод createRenderRoot.

Задание shadowRootOptions

Самый простой способ настроить корень рендеринга — задать статическое свойство shadowRootOptions. Реализация createRenderRoot по умолчанию передаёт shadowRootOptions в качестве аргумента options методу attachShadow при создании теневого корня компонента. Это свойство можно задать, чтобы настроить любые параметры, допустимые в словаре ShadowRootInit, например mode и delegatesFocus.

class DelegatesFocus extends LitElement {
  static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true};
}

Дополнительную информацию см. в разделе Element.attachShadow() на MDN.

Реализация createRenderRoot

Реализация createRenderRoot по умолчанию создаёт открытый теневой корень и добавляет в него стили, заданные в поле класса static styles. Дополнительную информацию о стилях см. в разделе Стили.

Чтобы настроить корень рендеринга компонента, реализуйте createRenderRoot и верните узел, в который нужно выполнить рендеринг шаблона.

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

Рендеринг в дочерние элементы. Обычно не рекомендуется выполнять рендеринг в дочерние элементы, а не в теневой DOM. Ваш элемент не будет иметь доступа к области видимости DOM и стилей и не сможет компоновать элементы во внутреннем DOM.

Редактировать эту страницу

© Google LLC
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://lit.dev/docs/v2/components/shadow-dom/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API