自定义HTMLElement的connectedCallback()中的textContent为空

Jon*_*son 2 javascript web-component web custom-element native-web-component

connectedCallback()我的自定义元素的方法中,textContent它作为空字符串返回。

本质上,我的代码可以归结为以下内容...

class MyComponent extends HTMLElement{
    constructor() {
        super()

        console.log(this.textContent) // not available here, but understandable
    }           

    connectedCallback() {
        super.connectedCallback() // makes no difference if present or not

        console.log(this.textContent) // not available here either, but why?!
    }
}

customElements.define('my-component', MyComponent);     
Run Code Online (Sandbox Code Playgroud)

还有HTML ...

<my-component>This is the content I need to access</my-component>
Run Code Online (Sandbox Code Playgroud)

从阅读方面看,connectedCallback()这听起来好像是在将元素添加到DOM后就被调用了,所以我希望textContent属性应该是有效的。

我正在使用Chrome 63,如果有帮助...

con*_*exo 6

您面临的问题与我们的团队在当前项目中遇到的问题基本相同:

connectedCallback在Chrome浏览器中不能保证对子对象进行解析。具体来说,在升级情况下,依赖子项是有效的,但是如果在浏览器解析该元素时预先知道该元素,则将无法使用子项。因此,如果将webcomponents.js包放在的末尾body,它至少可以可靠地用于您之前拥有的静态文档(但是如果您在DOMContentLoaded使用document.write之后以编程方式创建元素,仍然会失败(无论如何您都不应这样做) )。这基本上就是您发布的解决方案。

更糟的是,“自定义元素”规范v1中没有生命周期挂钩可以确保对子元素的访问

因此,如果您的自定义元素依赖于子级来设置(而像您这样的简单textNode textContent 就是子级节点),那么这就是我们经过一周的大量研究和测试后能够提取的内容(Google AMP小组也这样做了)):

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 to child
    // elements once using this as a guard
  }
}

customElements.define('my-component', MyComponent)
Run Code Online (Sandbox Code Playgroud)
<my-component>textNode here</my-component>
Run Code Online (Sandbox Code Playgroud)

更新:很早以前,自定义元素polyfill document-register-element(例如,Google AMP正在使用)的作者Andrea Giammarchi(@webreflection)大力倡导将这样的a引入parsedCallback自定义元素的API中上面的代码,并html-parsed-element从中创建一个包,可能会帮助您:

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

您只需从HTMLParsedElement包提供的基类(而不是HTMLElement)派生您的元素。该基类又继承自HTMLElement


Bob*_*Bob 5

您可以使用插槽和 slotchange 事件访问内容(插槽获取主机标记内容。)

(function(){
    
    class MyComponent extends HTMLElement {
        
        constructor() {
            super();
            
            let slot = document.createElement('slot') ;

            slot.addEventListener('slotchange', function(e) {
                let nodes = slot.assignedNodes();
                console.log('host text: ',nodes[0].nodeValue);                               
            });
  
            const shadowRoot = this.attachShadow({mode: 'open'});
            shadowRoot.appendChild(slot);     
        }
        
    }
            
    window.customElements.define('my-component', MyComponent);
})();
Run Code Online (Sandbox Code Playgroud)
<my-component>This is the content I need to access</my-component>
Run Code Online (Sandbox Code Playgroud)


Jon*_*son 2

我设法通过仅customElements.define('my-component', MyComponent);在 DOMContentLoaded 事件触发后调用来解决此问题。

document.addEventListener('DOMContentLoaded', function() {
    customElements.define('my-component', MyComponent);   
}
Run Code Online (Sandbox Code Playgroud)

这种行为看起来有点奇怪,因为您期望connectedCallback只有在节点插入 DOM 并完全准备好进行操作时才会触发。