如何在另一个自定义元素的影子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
。
答案 0 :(得分:2)
原因是在某些情况下,只要浏览器遇到自定义元素的开始标签 (其中没有子元素),就会在某些情况下立即调用自定义元素的connectedCallback()
正在解析,因此不可用。这确实例如如果您先定义了元素,然后在浏览器中解析HTML,就会在Chrome中发生这种情况。
这就是为什么外部let inputs = this.getElementsByTagName('spk-input')
的{{1}}方法中的update()
无法找到任何元素的原因。不要让误导那里的console.log输出迷惑自己。
我最近刚刚深入探讨了该主题,并建议使用<spk-root>
类的解决方案:
https://gist.github.com/franktopel/5d760330a936e32644660774ccba58a7
Andrea Giammarchi(HTMLBaseElement
polyfill的作者,在不支持的浏览器中用于自定义元素)已经接受了该解决方案建议,并从中创建了一个npm包:
只要不需要动态创建自定义元素,最简单,最可靠的解决方案就是通过将元素定义脚本放在{{的末尾)来创建升级方案。 1}}。
如果您对该主题的讨论感兴趣(请多读!)
这是全部要点:
Web组件规范v1存在一个巨大的实际问题:
在某些情况下,当元素的子节点尚不可用时,将调用document-register-element
。
这在某些情况下会导致Web组件无法正常运行。
请参阅https://github.com/w3c/webcomponents/issues/551以供参考。
为解决此问题,我们在团队中创建了一个body
类,该类用作扩展自定义元素的新类。
connectedCallback
依次继承自HTMLBaseElement
(自治自定义元素必须在其原型链中的某个位置派生)。
HTMLBaseElement
添加了两件事:
HTMLElement
方法,它负责正确的时间安排(即确保子节点可访问),然后在组件实例上调用HTMLBaseElement
。setup
布尔属性,默认为childrenAvailableCallback()
,在组件初始设置完成后应设置为parsed
。这是为了确保例如子事件侦听器的连接从未超过一次。false
扩展上述内容的示例组件:
true
答案 1 :(得分:-1)
HTMLCollection inputs
确实具有length属性,如果将其记录在update函数中,您将看到它的值为2。您也可以在for循环中遍历input集合,只要它在内部即可update()函数。
如果要在更新函数之外的循环中访问值,则可以将HTMLCollection存储在SpektacularInput类范围之外声明的变量中。
我想还有其他方法来存储值,具体取决于您要完成的工作,但是希望这能回答您的最初问题“如何从update()遍历spk-root中的spk-input元素。方法?”
class SpektacularInput extends HTMLElement {
constructor() {
super();
}
}
window.customElements.define('spk-input', SpektacularInput);
let template = document.createElement('template');
template.innerHTML = `
<canvas id='spektacular'></canvas>
<slot></slot>
`;
// declare outside variable
let inputsObj = {};
class SpektacularRoot extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(template.content.cloneNode(true));
}
update() {
// store on outside variable
inputsObj = this.getElementsByTagName('spk-input');
// use in the function
let inputs = this.getElementsByTagName('spk-input');
console.log("inside length: " + inputs.length)
for(let i = 0; i < inputs.length; i++){
console.log("inside input " + i + ": " + inputs[i]);
}
}
connectedCallback() {
this.update();
}
}
window.customElements.define('spk-root', SpektacularRoot);
console.log("outside length: " + inputsObj.length);
for(let i = 0; i < inputsObj.length; i++){
console.log("outside input " + i + ": " + inputsObj[i]);
}
<spk-root>
<spk-input></spk-input>
<spk-input></spk-input>
</spk-root>
希望有帮助, 干杯!