根据 custom element specification ,
The element must not gain any attributes or children, as this violates the expectations of consumers who use the
createElement
orcreateElementNS
methods.
在这种情况下,Firefox 和 Chrome 都会正确抛出错误。但是,附加影子 DOM 时,不会出现任何错误(在任一浏览器中)。
火狐:
NotSupportedError: Operation is not supported
Chrome :
Uncaught DOMException: Failed to construct 'CustomElement': The result must not have children
没有影子DOM
function createElement(tag, ...children) {
let root;
if (typeof tag === 'symbol') {
root = document.createDocumentFragment();
} else {
root = document.createElement(tag);
}
children.forEach(node => root.appendChild(node));
return root;
}
customElements.define(
'x-foo',
class extends HTMLElement {
constructor() {
super();
this.appendChild(
createElement(
Symbol(),
createElement('div'),
),
);
}
},
);
createElement('x-foo');
使用 shadow DOM
function createElement(tag, ...children) {
let root;
if (typeof tag === 'symbol') {
root = document.createDocumentFragment();
} else {
root = document.createElement(tag);
}
children.forEach(node => root.appendChild(node));
return root;
}
customElements.define(
'x-foo',
class extends HTMLElement {
constructor() {
super();
// it doesn't matter if this is open or closed
this.attachShadow({ mode: 'closed' }).appendChild(
createElement(
Symbol(),
createElement('div'),
),
);
}
},
);
createElement('x-foo');
请注意:为了查看示例,you need to be using (至少)以下之一:Firefox 63、Chrome 67、Safari 10.1。不支持边缘。
我的问题如下:
根据规范,行为是否正确?
将子节点添加到根节点会导致 DOM 重排;如果没有影子 DOM,如何避免这种情况?
最佳答案
每次创建元素都是通过构造函数完成的。但是,当调用构造函数时,没有子项也没有任何属性,这些都是在创建组件之后添加的。
即使该元素是在 HTML 页面中定义的,它仍然是由代码使用构造函数创建的,然后由解析 HTML 页面中的 DOM 的代码添加属性和子元素。
调用构造函数时,没有子元素,您不能添加它们,因为 DOM 解析器可能会在构造函数完成后立即添加它们。相同的规则适用于属性。
目前没有办法指定 shadowDOM 或 shadowDOM child ,除非通过 JS 代码。 DOM 解析器不会向 shadowDOM 添加任何子元素。
因此根据规范,在构造函数中访问、更改属性或子项或对其进行任何操作都是非法的。但是,由于 DOM 解析器无法向组件 shadowDOM 添加任何非法内容。
在不使用 shadowDOM 时,我已经解决了这个问题,方法是使用在构造函数中创建的内部模板元素,然后在调用 connectedCallback
时将其作为子元素放置。
// Class for `<test-el>`
class TestEl extends HTMLElement {
constructor() {
super();
console.log('constructor');
const template = document.createElement('template');
template.innerHTML = '<div class="name"></div>';
this.root = template.content;
this.rendered = false;
}
static get observedAttributes() {
return ['name'];
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (oldVal !== newVal) {
console.log('attributeChangedCallback', newVal);
this.root.querySelector('.name').textContent = newVal;
}
}
connectedCallback() {
console.log('connectedCallback');
if (!this.rendered) {
this.rendered = true;
this.appendChild(this.root);
this.root = this;
}
}
// `name` property
get name() {
return this.getAttribute('name');
}
set name(value) {
console.log('set name', value);
if (value == null) { // Check for null or undefined
this.removeAttribute('name');
}
else {
this.setAttribute('name', value)
}
}
}
// Define our web component
customElements.define('test-el', TestEl);
const moreEl = document.getElementById('more');
const testEl = document.getElementById('test');
setTimeout(() => {
testEl.name = "Mummy";
const el = document.createElement('test-el');
el.name = "Frank N Stein";
moreEl.appendChild(el);
}, 1000);
<test-el id="test" name="Dracula"></test-el>
<hr/>
<div id="more"></div>
此代码在构造函数中创建一个模板并使用 this.root
来引用它。
调用 connectedCallback
后,我将模板插入 DOM 并将 this.root
更改为指向 this
,这样我对元素的所有引用仍然存在工作。
这是一种让您的组件始终能够保持其子项正确的快速方法,而无需使用 shadowDOM 并且仅在调用 connectedCalback
后才将模板作为子项放入 DOM 中。
关于html - 将影子 DOM 附加到自定义元素可以消除错误,但为什么呢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53180428/