自定义HTMLElement的connectedCallback()中的textContent为空 [英] textContent empty in connectedCallback() of a custom HTMLElement

查看:245
本文介绍了自定义HTMLElement的connectedCallback()中的textContent为空的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Within the connectedCallback() method of my custom element the textContent is returned as an empty string.

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

Essentially my code boils down to the following...

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

And the HTML...

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

通过阅读有关connectedCallback()的信息,听起来好像在元素添加到DOM后就被调用了,所以我希望textContent属性应该是有效的.

From reading about connectedCallback() it sounds like it's called once the element has been added to the DOM so I would expect that the textContent property should be valid.

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

I'm using Chrome 63 if it helps...

推荐答案

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

The issue you're facing is essentially the same our team has run into in our current project:

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

connectedCallback in Chrome does not guarantee children are parsed. Specifically, relying on children works in the upgrade case, but does not work if the element is known upfront when the browser parses it. So if you place your webcomponents.js bundle at the end of the body, it at least reliably works for the static document you have up until then (but will still fail if you create the element programmatically after DOMContentLoaded using document.write(which you shouldn't anyway)). This is basically what you have posted as your solution.

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

To make matters worse, there is no lifecycle hook that does guarantee child element access in Custom Elements spec v1.

因此,如果您的自定义元素依赖于子级来设置(而像您的textContent 这样的简单textNode是子级节点),那么我们就能做到这一点经过一周的大量研究和测试(这是Google AMP团队所做的事情)以及):

So if your custom element relies on children to setup (and a simple textNode like your textContent is a child node), this is what we were able to extract after a week of excessive research and testing (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)

<my-component>textNode here</my-component>

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

Update: Already quite a while ago Andrea Giammarchi (@webreflection), the author of the custom elements polyfill document-register-element (which e.g. is being used by Google AMP), who is a strong advocate of introducing such a parsedCallback to the custom elements' API, has taken the above code and create a package html-parsed-element from it which might help you:

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

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

You simply derive your elements from the HTMLParsedElement base class that package provides (instead of HTMLElement). That base class, in turn, inherits from HTMLElement.

这篇关于自定义HTMLElement的connectedCallback()中的textContent为空的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆