Работа с Shadow DOM
Компоненты Lit используют Shadow DOM для инкапсуляции своего DOM. Shadow DOM позволяет добавить к элементу отдельное изолированное и инкапсулированное дерево DOM. Инкапсуляция DOM — ключ к обеспечению совместимости с любым другим кодом, включая другие веб-компоненты или компоненты Lit, работающие на странице.
Shadow DOM предоставляет три преимущества:
- Область видимости DOM. API DOM, такие как
document.querySelector, не находят элементы в теневом DOM компонента, поэтому глобальным скриптам сложнее случайно нарушить работу компонента. - Область видимости стилей. Вы можете писать инкапсулированные стили для теневого DOM, которые не влияют на остальную часть дерева DOM.
- Композиция. Теневой корень компонента, содержащий его внутренний DOM, отделён от дочерних элементов компонента. Вы можете выбрать, как отображать дочерние элементы во внутреннем DOM компонента.
Дополнительная информация о Shadow DOM:
- Shadow DOM v1: автономные веб-компоненты на сайте Web Fundamentals.
- Использование Shadow DOM на MDN.
Старые браузеры. В старых браузерах, где нативный Shadow DOM недоступен, можно использовать полифилы веб-компонентов. Обратите внимание, что модуль polyfill-support Lit необходимо загружать вместе с полифилами веб-компонентов. Подробности см. в разделе Требования для старых браузеров.
Доступ к узлам в Shadow 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.
Отображение в дочерние элементы. Как правило, отображение в дочерние элементы, а не в Shadow DOM не рекомендуется. Ваш элемент не сможет использовать область видимости DOM или стилей, а также компоновать элементы во внутреннем DOM.
© Google LLC
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://lit.dev/docs/components/shadow-dom/