javascript - 迭代自定义元素中的 HTMLCollection
问题描述
如何在另一个自定义元素的 shadow dom 中迭代一个自定义元素的实例?HTMLCollections 似乎没有按预期运行。(当谈到香草js时,我是一个jQuerian和一个新手,所以我确信我在某个地方犯了一个明显的错误)。
HTML
<spk-root>
<spk-input></spk-input>
<spk-input></spk-input>
</spk-root>
自定义元素定义
对于spk-input
:
class SpektacularInput extends HTMLElement {
constructor() {
super();
}
}
window.customElements.define('spk-input', SpektacularInput);
对于spk-root
:
let template = document.createElement('template');
template.innerHTML = `
<canvas id='spektacular'></canvas>
<slot></slot>
`;
class SpektacularRoot extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(template.content.cloneNode(true));
}
update() {
let inputs = this.getElementsByTagName('spk-input')
}
connectedCallback() {
this.update();
}
}
window.customElements.define('spk-root', SpektacularRoot);
这是我不明白的部分。在update()
方法内部:
console.log(inputs)
返回一个 HTMLCollection:
console.log(inputs)
// output
HTMLCollection []
0: spk-input
1: spk-input
length: 2
__proto__: HTMLCollection
但是,HTMLCollection 不能使用for
循环进行迭代,因为它没有长度。
console.log(inputs.length)
// output
0
搜索 SO 显示 HTMLCollections 类似于数组,但不是数组。尝试使用或扩展运算符使其成为一个数组Array.from(inputs)
会导致一个空数组。
这里发生了什么?如何迭代方法中的spk-input
元素?spk-root
update()
我正在使用 gulp-babel 和 gulp-concat 并使用 Chrome。如果需要更多信息,请告诉我。提前致谢。
编辑console.log(inputs.length)
:为了澄清,从输出中调用而不是.update()
0
2
解决方案
原因是connectedCallback()
在某些情况下,只要浏览器遇到自定义元素的开始标签,就会调用自定义元素,而子元素不会被解析,因此不可用。例如,如果您预先定义元素并且浏览器解析 HTML,这确实会发生在 Chrome 中。
这就是为什么let inputs = this.getElementsByTagName('spk-input')
在您update()
的外部方法中<spk-root>
找不到任何元素的原因。不要让自己被误导性的 console.log 输出所迷惑。
我最近刚刚深入研究了这个主题,并提出了一个使用HTMLBaseElement
类的解决方案:
https://gist.github.com/franktopel/5d760330a936e32644660774ccba58a7
Andrea Giammarchi(polyfill document-register-element
for custom elements in non-supporting browsers 的作者)接受了该解决方案的建议,并从中创建了一个 npm 包:
只要您不需要动态创建自定义元素,最简单和最可靠的修复方法就是通过将元素定义脚本放在body
.
如果您对该主题的讨论感兴趣(长读!):
这是完整的要点:
HTMLBaseElement类解决children解析前调用connectedCallback的问题
Web 组件规范 v1 存在一个巨大的实际问题:
在某些情况下connectedCallback
,当元素的子节点不可用时调用。
这使得 Web 组件在依赖子组件进行设置的情况下无法正常工作。
请参阅https://github.com/w3c/webcomponents/issues/551以供参考。
为了解决这个问题,我们HTMLBaseElement
在团队中创建了一个类,作为扩展自主自定义元素的新类。
HTMLBaseElement
反过来继承自HTMLElement
(自治自定义元素必须从其原型链中的某个点派生)。
HTMLBaseElement
添加两件事:
- 一种
setup
处理正确时间(即确保子节点可访问)然后调用childrenAvailableCallback()
组件实例的方法。 - 一个
parsed
布尔属性,默认false
设置为true
在组件初始设置完成时设置。这是为了作为一个守卫,以确保例如子事件侦听器永远不会被附加一次以上。
HTMLBaseElement
class HTMLBaseElement extends HTMLElement {
constructor(...args) {
const self = super(...args)
self.parsed = false // guard to make it easy to do certain stuff only once
self.parentNodes = []
return self
}
setup() {
// collect the parentNodes
let el = this;
while (el.parentNode) {
el = el.parentNode
this.parentNodes.push(el)
}
// check if the parser has already passed the end tag of the component
// in which case this element, or one of its parents, should have a nextSibling
// if not (no whitespace at all between tags and no nextElementSiblings either)
// resort to DOMContentLoaded or load having triggered
if ([this, ...this.parentNodes].some(el=> el.nextSibling) || document.readyState !== 'loading') {
this.childrenAvailableCallback();
} else {
this.mutationObserver = new MutationObserver(() => {
if ([this, ...this.parentNodes].some(el=> el.nextSibling) || document.readyState !== 'loading') {
this.childrenAvailableCallback()
this.mutationObserver.disconnect()
}
});
this.mutationObserver.observe(this, {childList: true});
}
}
}
扩展上述内容的示例组件:
class MyComponent extends HTMLBaseElement {
constructor(...args) {
const self = super(...args)
return self
}
connectedCallback() {
// when connectedCallback has fired, call super.setup()
// which will determine when it is safe to call childrenAvailableCallback()
super.setup()
}
childrenAvailableCallback() {
// this is where you do your setup that relies on child access
console.log(this.innerHTML)
// when setup is done, make this information accessible to the element
this.parsed = true
// this is useful e.g. to only ever attach event listeners once
// to child element nodes using this as a guard
}
}
推荐阅读
- office365 - 使用 addFileAttachmentAsync 附加 .msg 文件失败
- python - SQLAlchemy 和 PyTest:如何在测试期间更改数据库?
- c# - c# Linq/DBContext 在日期或更大或更小日期之间查询取决于传递的参数
- php - Samba 文件系统中文件的 file_get_contents 超时
- python - 数据清洗 - ifelse 语句
- boolean-logic - 难以理解布尔恒等律
- jquery - 在 pug 模板中包含 jquery
- android - java.lang.ClassNotFound:在路径上找不到类“android.support.multidex.MultiDexApplication”:DexPathList
- java - 获取 LUIS APP 的所有意图和实体列表的 API 是什么
- javascript - Javascript待办事项列表问题