#javascript #html #web-component #shadow-dom
#javascript #HTML #веб-компонент #shadow-dom
Вопрос:
Допустим, у меня есть такой код:
class MyElem extends HTMLElement {
constructor() {
super();
let templateContent = document.getElementById('template-elem').content;
this.innerHTML = templateContent.cloneNode(true);
}
}
window.customElements.define('my-elem', MyElem);
<template id="template-elem">
<div class="a">
<div class="b">b</div>
<div class="c">c</div>
</div>
</template>
<my-elem></my-elem>
Почему это не работает? В инспекторе Chrome пользовательский элемент не содержит HTML внутри него. Я также пытался делать:
this.append(templateContent.cloneNode(true));
но это также привело к пустому HTML-дереву.
Во всех руководствах упоминается использование shadow DOM следующим образом:
this.attachShadow({mode: 'open'}).appendChild(templateContent.cloneNode(true));
и хотя это работает, это заставляет вас использовать Shadow DOM для вашего пользовательского элемента. Нет ли способа просто добавить HTML-код шаблона к вашему пользовательскому элементу, не требуя использования Shadow DOM? Я бы предпочел просто использовать глобальный стиль CSS в моем небольшом варианте использования.
Ответ №1:
Вы попадаете в множество ловушек, как и все в своих первых приключениях с компонентами.
- Пользовательские элементы (строго говоря, веб-компонентами являются только элементы с ShadowDOM) имеют фазы жизненного цикла и обратные вызовы.
Эта схема: https://andyogo.github.io/custom-element-reactions-diagram / необходимо понять.
Вы хотите добавить содержимое DOM наconstructor
этапе; но на этом этапе еще нет элемента DOM.
Только вconnectedCallback
DOM можно добавлять содержимое.
С ShadowDOM это другая история, его «DocumentFragment» доступен вconstructor
, вы можете установить содержимое, но это еще не элемент DOM!connectedCallback
Сообщает вам, когда ваш пользовательский элемент был присоединен к DOM. - Содержимое шаблонов — это DocumentFragment, но ваш
.innerHTML
ожидает строку.
Поскольку (в вашем использовании)<template>
является элементом DOM, вы можете прочитать его innerHTML (см. Ниже)
Итак, да, пользовательские элементы без ShadowDOM возможны:
Вы увидите <template>
содержимое дважды, демонстрируя 2 способа добавления содержимого.
<script>
customElements.define("my-element", class extends HTMLElement {
connectedCallback() {
let template = document.getElementById(this.nodeName);
this.innerHTML = template.innerHTML;
this.append(template.content.cloneNode(true))
}
})
</script>
<template id="MY-ELEMENT">
Hello, I am an Element!
</template>
<my-element></my-element>
Это то constructor
, где вы готовите свой элемент
Это constructor
также выполняется, когда вы это делаете document.createElement("my-element")
.
connectedCallback
Запускается, когда ваш элемент добавляется в DOM
Если вы не укажете метод, запускается метод из его родительского класса, поэтому в приведенном выше коде выполняется (по умолчанию) constructor
из htmlэлемента.
Вот почему вам нужен super()
свой собственный constructor
… для выполнения элемента constructor
from HTMLElement .
Примечание:
constructor(){
let template = document.getElementById("MY-ELEMENT").content.cloneNode(true);
super().attachShadow({mode:"open").append(template);
}
это полностью корректный код; Документация Google, в которой говорится, что «сначала нужно запустить super», неверна.
Вам нужно выполнить, super()
прежде чем вы сможете получить доступ к собственной области элементов с помощью this
Вот почему я предпочитаю:
constructor(){
// do anything you want here, but you can not use 'this'
super() // Sets AND Returns 'this'
.attachShadow({mode:"open") // both Sets AND Returns this.shadowRoot
.append(document.getElementById(this.nodeName).content.cloneNode(true));
}
Обратите внимание, что append() не был доступен в IE; поэтому программисты oldskool не будут знать о его универсальности: https://developer.mozilla.org/en-US/docs/Web/API/Element/append
Когда ваши приключения с компонентами будут связаны с наследованием классов;
вы вызываете родительские методы с:
connectedCallback(){
super.connectedCallback()
}
Комментарии:
1. Ах, хорошо, большое спасибо! Да, мне было интересно, почему я могу добавить в конструктор для shadow DOM, но не обычный DOM. Это имеет смысл.
Ответ №2:
Наиболее простой реализацией пользовательского элемента будет:
class MyComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = `<div>Hello world</div>`
}
}
customElements.define('my-component', MyComponent)
my-component {
display: block;
border: 1px dotted #900
}
<my-component></my-component>
Однако, если вы не используете Shadow DOM, вы не сможете инкапсулировать CSS, но должны стилизовать компонент с помощью внешней таблицы стилей.
Самый простой способ написать компонент с теневым DOM будет выглядеть так:
class MyOtherComponent extends HTMLElement {
constructor() {
super()
this.shadow = this.attachShadow({ mode: "open" })
}
connectedCallback() {
this.shadow.innerHTML = `
<style>
:host {
display: block;
border: 1px dotted #900
}
</style>
<div class="component">Hello World!</div>
`
}
}
customElements.define('my-other-component', MyOtherComponent)
<my-other-component></my-other-component>
Таким образом, у вас будет немного больше накладных расходов, но компонент действительно инкапсулирован.