【问题标题】:Iterate over HTMLCollection in custom element迭代自定义元素中的 HTMLCollection
【发布时间】:2019-04-07 16:20:15
【问题描述】:

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

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) 或扩展运算符使其成为一个数组会导致一个空数组。

这里发生了什么?如何通过update() 方法迭代spk-root 中的spk-input 元素?

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


编辑:澄清一下,从内部调用console.log(inputs.length)update()输出0而不是2

【问题讨论】:

    标签: javascript html shadow-dom custom-element


    【解决方案1】:

    原因是在某些情况下,只要浏览器遇到自定义元素的开始标签,就会调用自定义元素的connectedCallback()子元素不被解析,因此不可用。这确实是例如如果您预先定义元素,然后浏览器解析 HTML,则会在 Chrome 中发生。

    这就是为什么let inputs = this.getElementsByTagName('spk-input') 在外部&lt;spk-root&gt;update() 方法中找不到任何元素的原因。不要让自己被误导性的 console.log 输出所迷惑。

    我最近刚刚深入研究了这个主题,并提出了一个使用 HTMLBaseElement 类的解决方案:

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

    Andrea Giammarchi(document-register-element polyfill for custom elements in non-supporting browsers 的作者)采纳了该解决方案建议并从中创建了一个 npm 包:

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

    只要您不需要动态创建自定义元素,最简单和最可靠的解决方法是通过将元素定义脚本放在 @987654331 的末尾来创建 升级 场景@。

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

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

    这是完整的要点:

    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
      }
    }
    

    【讨论】:

      【解决方案2】:

      HTMLCollection inputs 确实有一个长度属性,如果你在更新函数中记录它,你会看到它的值为 2。你也可以在 for 循环中遍历输入集合,只要它在更新中() 函数。

      如果您想在更新函数之外访问循环中的值,您可以将 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>

      希望对你有帮助, 干杯!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-10-02
        • 1970-01-01
        • 1970-01-01
        • 2015-09-25
        • 2014-05-10
        相关资源
        最近更新 更多