5

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

4

3 回答 3

13

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

connectedCallback在 Chrome 中保证孩子被解析。具体来说,依赖子级在升级情况下有效,但如果在浏览器解析元素时预先知道元素,则不起作用。因此,如果您将捆绑webcomponents.js包放在body.DOMContentLoaded)。这基本上就是您发布的解决方案。

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

因此,如果您的自定义元素依赖于子节点进行设置(并且像您这样的简单 textNodetextContent 子节点),这就是我们在经过一周的过度研究和测试后能够提取的内容(这也是 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)
<my-component>textNode here</my-component>

更新:很久以前 Andrea Giammarchi (@webreflection),自定义元素 polyfill 的作者document-register-element(例如,Google AMP 正在使用它),他强烈主张将这种元素引入parsedCallback自定义元素的 API,已经采取上面的代码并html-parsed-element从中创建了一个包,这可能会对您有所帮助:

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

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

于 2018-10-19T00:36:19.233 回答
6

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

于 2019-01-24T22:00:58.127 回答
3

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

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

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

于 2018-02-02T10:39:37.273 回答