迭代自定义元素中的 HTMLCollection

2024-02-11

如何在另一个自定义元素的 Shadow dom 中迭代一个自定义元素的实例? HTMLCollections 似乎没有按预期运行。 (我是一个 jQuerian,而且是普通 js 的新手,所以我确信我在某个地方犯了一个明显的错误)。

HTML

<spk-root>
  <spk-input></spk-input>
  <spk-input></spk-input>
</spk-root>

自定义元素定义

For spk-input:

class SpektacularInput extends HTMLElement {
  constructor() {
    super();
  }
}
window.customElements.define('spk-input', SpektacularInput);

For 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

搜索发现 HTMLCollections 是类似数组的,但不是数组。尝试使用它使其成为数组Array.from(inputs)或者扩展运算符会产生空数组。

这里发生了什么?我怎样才能迭代spk-input内的元素spk-root来自update() method?

我正在使用 gulp-babel 和 gulp-concat 并使用 Chrome。如果需要更多信息,请告诉我。提前致谢。


Edit: 澄清一下,打电话console.log(inputs.length) from within the update()输出0代替2.


原因将是connectedCallback()在某些情况下,一旦浏览器满足以下条件,就会调用自定义元素开始标签自定义元素的,孩子们没有被解析,因此不可用。这确实例如如果您预先定义元素,然后浏览器解析 HTML,Chrome 中就会发生这种情况。

因此let inputs = this.getElementsByTagName('spk-input')在你的update()外层的方法<spk-root>找不到任何元素。不要让自己被误导性的 console.log 输出所愚弄。

我最近刚刚深入研究了这个主题,并提出了一个使用HTMLBaseElement class:

https://gist.github.com/franktopel/5d760330a936e32644660774ccba58a7 https://gist.github.com/franktopel/5d760330a936e32644660774ccba58a7

安德里亚·吉亚玛奇 (Andrea Giammarchi)document-register-elementpolyfill 用于在不支持的浏览器中自定义元素)已采纳该解决方案建议并从中创建了一个 npm 包:

https://github.com/WebReflection/html-parsed-element https://github.com/WebReflection/html-parsed-element

只要您不需要动态创建自定义元素,最简单、最可靠的解决方法就是创建upgrade通过将元素定义脚本放在末尾的场景body.

如果您对有关该主题的讨论感兴趣(长读!):

https://github.com/w3c/webcomponents/issues/551 https://github.com/w3c/webcomponents/issues/551

这是完整的要点:

HTMLBaseElement 类解决了在解析子级之前调用connectedCallback 的问题

Web 组件规范 v1 存在一个巨大的实际问题:

在某些情况下connectedCallback当元素的子节点尚不可用时调用。

这使得 Web 组件在依赖其子组件进行设置的情况下无法正常工作。

See https://github.com/w3c/webcomponents/issues/551 https://github.com/w3c/webcomponents/issues/551以供参考。

为了解决这个问题,我们创建了一个HTMLBaseElement我们团队中的类作为新类来扩展自主自定义元素。

HTMLBaseElement依次继承自HTMLElement(哪些自主定制元素必须源自其原型链中的某个点)。

HTMLBaseElement添加两件事:

  • a setup负责正确计时的方法(即确保子节点可访问),然后调用childrenAvailableCallback()在组件实例上。
  • a parsed布尔属性,默认为false并且应该设置为true当组件初始设置完成后。这是为了充当警卫以确保例如子事件侦听器永远不会附加多次。

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
  }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

迭代自定义元素中的 HTMLCollection 的相关文章

随机推荐