【问题标题】:textContent empty in connectedCallback() of a custom HTMLElement自定义 HTMLElement 的 connectedCallback() 中的 textContent 为空
【发布时间】:2018-01-29 10:00:33
【问题描述】:

在我的自定义元素的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);     

还有 HTML...

<my-component>This is the content I need to access</my-component>

从阅读有关connectedCallback() 的内容来看,它听起来好像是在将元素添加到 DOM 后调用的,所以我希望 textContent 属性应该是有效的。

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

【问题讨论】:

标签: javascript web-component custom-element native-web-component


【解决方案1】:

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

connectedCallback in Chrome does not guarantee children are parsed. 具体来说,在 upgrade 的情况下依赖子元素是可行的,但如果元素在浏览器解析时预先知道,则不起作用。因此,如果您将 webcomponents.js 捆绑包放在 body 的末尾,它至少可以可靠地用于您之前拥有的静态文档(但如果您在 DOMContentLoaded 使用 document.写(无论如何你都不应该这样做))。这基本上就是您发布的解决方案。

更糟糕的是,自定义元素规范 v1 中没有保证子元素访问的生命周期挂钩

因此,如果您的自定义元素依赖于子节点进行设置(并且像您的 textContent 这样的简单 textNode 子节点),这就是我们能够做到的经过一周的过度研究和测试后提取 (which is what the Google AMP team does as well):

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)
&lt;my-component&gt;textNode here&lt;/my-component&gt;

更新:很久以前 Andrea Giammarchi (@webreflection),自定义元素 polyfill document-register-element 的作者(例如,正在被 Google AMP 使用),他是一个坚定的倡导者在将这样的parsedCallback 引入自定义元素的 API 时,已采用上述代码并从中创建了一个包 html-parsed-element,这可能会对您有所帮助:

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

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

【讨论】:

    【解决方案2】:

    您可以使用插槽和 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);
    })();
    &lt;my-component&gt;This is the content I need to access&lt;/my-component&gt;

    【讨论】:

    • 这在子元素也是使用 shadow dom 的自定义元素时不起作用。回调中的节点将为空。
    【解决方案3】:

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

    document.addEventListener('DOMContentLoaded', function() {
        customElements.define('my-component', MyComponent);   
    }
    

    这种行为似乎有点奇怪,因为您预计 connectedCallback 只会在节点被插入 DOM 并完全准备好被操作时触发。

    【讨论】:

    • 这是一个(不好的)解决方法,其他答案是解决方案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-22
    • 2023-03-18
    • 1970-01-01
    • 2019-04-23
    • 1970-01-01
    • 2020-02-28
    相关资源
    最近更新 更多