我们什么时候可以使用 javascript 访问自定义组件的 children 元素?

when can we access the children elements of a custom component using javascript?

所以我正在尝试使用 vanilla javascript 构建一个自定义组件,它将根据它拥有的 children 的数量来做某些事情,这意味着它必须计算所说的 [=31] =]

如果我有以下标记(其中自定义组件称为“my-component”)

<my-component>
  <div></div>
  <!-- ...arbitrary number of child elements -->
</my-component>

以及 <head></head> 中的以下 javascript 代码以确保它在 <body></body> 被解析之前加载

class MyComponent extends HTMLElement {

  constructor(){
    super()
    this.children.length
    //do stuff depending on the number of children
  }

  //or

  connectedCallback () {
    this.children.length
    //do stuff depending on the numbre of children
  }

}

customElements.define("my-component",MyComponent)

this.children.length 在这两种情况下都将 return 0,尽管元素随后显示在屏幕上,并且能够检查控制台上的自定义元素并获得 [=31 的预期数量=] 和 Element.children.length。我想这意味着 children 元素在 constructorconnectedCallback 是 运行.

时尚不可用

有什么方法可以在我的元素的 class 定义中指定一个函数,该函数将在 children 元素可用时触发,以便我可以对它们进行处理?我希望有一个“childElementsReady”回调或类似的东西,但我猜它不存在。我不知道是否有一种非常明显的方法来处理我所缺少的,因为这似乎是我应该能够相对轻松地完成的事情

A MutationObserver 是处理此问题的最佳方法。您可以在 connectedCallback 中设置一个来观察光的变化 DOM - 在这种情况下,仅观察 childList 就足够了:

class MyElement extends HTMLElement {
  constructor() {
    super();

    this.onMutation = this.onMutation.bind(this);
  }

  connectedCallback() {
    // Set up observer
    this.observer = new MutationObserver(this.onMutation);

    // Watch the Light DOM for child node changes
    this.observer.observe(this, {
      childList: true
    });
  }

  disconnectedCallback() {
    // remove observer if element is no longer connected to DOM
    this.observer.disconnect();
  }
  
  onMutation(mutations) {
    const added = [];

    // A `mutation` is passed for each new node
    for (const mutation of mutations) {
      // Could test for `mutation.type` here, but since we only have
      // set up one observer type it will always be `childList`
      added.push(...mutation.addedNodes);
    }
    
    console.log({
      // filter out non element nodes (TextNodes etc.)
      added: added.filter(el => el.nodeType === Node.ELEMENT_NODE),
    });
  }
}

customElements.define('my-element', MyElement);

每次将节点添加到 Light 时都会调用此处 onMutation DOM,因此您可以在此处处理任何设置。

请注意,根据 Light DOM 中的节点,当元素连接到 DOM 时,onMutation 可能会被调用多次,所以不可能说所有 children 在任何时候都是 'ready' - 相反,您必须处理每个突变。

我写这篇回复是为了回答我自己的问题,因为我发现这是一种有用的方法,可以在您有阴影 dom 时观察添加的 children,所以希望它可以帮助任何人在那种情况下,但 lamplightdev 的回答是最完整的,因为它在您使用阴影 dom 或不使用阴影时都有效,因此也请查看他的回答

如果您的自定义元素使用阴影 dom,您可以这样做:

class MyComponent extends HTMLElement {

  childAddedCustomCallback () {
    let watchedSlot = this 
      /*"this" here is not the custom element, but a slot that the custom
      element will have embedded in its shadow root, to which this function 
      will be attached as an event listener in the constructor*/
    
    let children = watchedSlot.assignedElements()
    let numberOfChildren = children.length
    
    //do stuff depending on the number of children
  }

  constructor(){
    super()
    let shadowRoot = this.attachShadow({mode:"open"})
    shadowRoot.innerHTML = "<slot></slot>"

    let slotToWatch = shadowRoot.querySelector("slot")
    slotToWatch.addEventListener("slotchange",this.childAddedCustomCallback)
  }

}

customElements.define("my-component",MyComponent)

这样,每次在自定义元素上添加一个child,它都会反映到未命名的slot,slot的事件监听器会在发生时触发回调,给你一个可靠和清晰的在元素可用时立即访问 children 的方法

如果您将 n children 添加到自定义元素,这有执行 n 次的缺点,例如如果您有以下标记:

<my-component>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</my-component>

不是在添加最后一个 child 元素时执行一次,而是执行 4 次(每个 child 执行一次),所以要当心