重要提示:几乎所有 IDE 都会自动推迟 javascript 的执行,这意味着您可能无法使用它们来重现此问题
在解析文档 HTML 之前执行定义组件的脚本的情况下使用 Web 组件 API 时,您最终会遇到一个不幸的问题,即在本机
connectedCallback
生命周期方法期间尚未解析元素的 insideHTML。这是因为该方法在开始解析其子元素之前在自定义元素的开始标记上执行。
<script>
customElements.define('my-element', MYElement);
</script>
<my-element>
<span>Content</span>
</my-element>
为了解决这个问题,我希望实现我自己的
parsedCallback
方法,但是,到目前为止我所拥有的方法不起作用。
这个想法是,因为浏览器将同步解析 HTML,所以我们可以知道,一旦解析“年轻”同级元素,组件内的所有内容都已完成解析。
因此,我向组件的父元素添加一个突变观察器,以便监听何时添加新的子节点(不是组件本身)。
此外,因为标记中可能没有“自然”同级,我还在组件后面附加一个 commentNode 以确保它始终有效。
class MYElement extends HTMLElement {
static counter = 0;
constructor() {
super();
this._id = MYElement.counter++;
}
connectedCallback() {
console.log(`${this._id} connected`);
console.log(`${this._id} has innerHTML on connected = ${this.innerHTML !== ""}`);
const parent = this.parentElement;
const marker = document.createComment(this._id);
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node !== this) {
console.log(`${this._id} younger sibling detected`);
this.parsedCallback();
marker.remove();
observer.disconnect();
}
});
});
});
observer.observe(parent, { childList: true });
parent.appendChild(marker);
}
parsedCallback() {
console.log(`${this._id} parsed`);
console.log(`${this._id} has innerHTML on parsed = ${this.innerHTML !== ""}`);
}
}
我的预期输出当然是
0 connected
0 has innerHTML on connected = false
0 younger sibling detected
0 parsed
0 has innerHTML on parsed = true
但是,除了最后一个控制台日志之外,我看到的是相同的内容:
0 has innerHTML on parsed = false
最简单的“解决方案”是:
connectedCallback() {
setTimeout(() => parsedCallback())
}
自 2017 年起为我工作
但是有一个障碍,如果您的 Web 组件 lightDOM 有 太多 DOM 节点
,则不起作用
(但是这样的野兽应该是 one Web 组件吗?)
我写了一篇(非常)长的 Dev.to 博客文章关于这个
connectedCallback
行为:根据你的措辞,我有一种暗示你已经读过那篇博文了……我在其中提到了 MutationObserver 但没有显示任何代码。
博客中没有的另一个替代解决方案(那里已经有太多信息)
是:
<my-component>
Muchos HTMLos
<img src="" onerror="this.getRootNode().host.parsedCallback()">
</my-component>
但这仅适用于具有 ShadowRoot 的 Web 组件
也许(我停止探索,因为即使对我来说这也太hacky)
onerror="this.parentNode.parsedCallback()"
否则
也许可以用你的
<marker>
技巧和这个<img src="" **onerror**="...">
技巧
我探索了许多其他选择,仍然坚持
setTimeout
只有一次需要添加100ms
connectedCallback() {
setTimeout(() => parsedCallback(),100)
}
但那是因为
.append()
与 DocumentFragment
组合添加了 DOM 异步