如何在另一个自定义元素的 shadow dom 中迭代一个自定义元素的实例? HTMLCollections 似乎不像预期的那样表现。 (当涉及到 vanilla 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 类的解决方案:

Andrea Giammarchi(不支持浏览器中自定义元素的 document-register-element polyfill 的作者)采纳了该解决方案建议并从中创建了一个 npm 包:

只要您不需要动态创建自定义元素,最简单和最可靠的解决方法是通过将元素定义脚本放在 body 末尾来创建 升级 场景。
如果您对该主题的讨论感兴趣(长篇阅读!):

这是完整的要点:
HTMLBaseElement 类解决了connectedCallback在解析children之前被调用的问题
Web 组件规范 v1 存在一个巨大的实际问题:
在某些情况下,当元素的子节点尚不可用时,会调用 connectedCallback
这使得 Web 组件在依赖其子组件进行设置的情况下无法正常工作。
请参阅 https://github.com/w3c/webcomponents/issues/551 以供引用。
为了解决这个问题,我们在我们的团队中创建了一个 HTMLBaseElement 类,作为扩展自主自定义元素的新类。HTMLBaseElement 又继承自 HTMLElement(自主自定义元素必须从其原型(prototype)链中的某个点派生)。HTMLBaseElement 添加了两件事:

  • 一个 setup 方法,负责处理正确的时间(即确保子节点可访问),然后在组件实例上调用 childrenAvailableCallback()
  • 一个 parsed bool 属性,默认为 false,并在组件初始设置完成时设置为 true。这是为了充当 guard 以确保例如子事件监听器永远不会被附加超过一次。

  • HTML基本元素
    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
      }
    }
    

    关于javascript - 迭代自定义元素中的 HTMLCollection,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53135217/

    10-11 23:11
    查看更多