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

问题描述 投票:0回答:4

所以我正在尝试使用 vanilla javascript 构建一个自定义组件,它将根据它拥有的子级数量执行某些操作,这意味着它必须计算所述子级的数量

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

<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
在这两种情况下都将返回 0,并且能够检查控制台上的自定义元素并使用
Element.children.length
获取预期的子元素数量。我想这意味着子元素在
constructor
connectedCallback
运行时尚不可用。

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

javascript html frontend web-component
4个回答
3
投票

A MutationObserver 是处理这个问题的最佳方法。您可以在

connectedCallback
中设置一个来观察 Light 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);

这里

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

请注意,根据 Light DOM 中的节点,当元素连接到 DOM 时,可以多次调用

onMutation
,因此不可能说所有子级在任何时候都已“准备好” - 相反,您可以必须处理每个出现的突变。


3
投票

我写这个回复是为了回答我自己的问题,因为我发现这是一种在你有影子 dom 时观察添加的孩子的有用方法,所以希望它可以帮助任何人在这种情况下,但 lamplightdev 的答案是最完整的答案无论你是否使用shadow dom,它都有效,所以也请查看他的答案

如果你的自定义元素使用了shadow 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)

这样,每次向自定义元素添加子元素时,它都会反映到未命名的插槽,并且插槽的事件侦听器将在发生这种情况时触发回调,为您提供可靠且清晰的方法来访问元素的子元素一旦有货

如果您向自定义元素添加 n 个子元素,例如,这会执行 n 次。如果您有以下标记:

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

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


1
投票

只是一些其他相关信息。有时,当您克隆或使用

importNode
时,子节点可能已经被插入,并且突变观察器不会捕获它们,在这种情况下,您将必须查看
this.children
来查找当前插入的子节点。

这里是来自 GH Issues 的相关评论

因此,对儿童有期望的自定义元素需要:

  • 检查connectedCallback中是否有孩子,做一些事情
  • 设置突变观察者并在添加节点时执行操作(这将在升级的情况下发生)。
  • 在disconnectedCallback中断开突变观察者

最后这是一个使用 Mutation Observer 的简单示例,只需运行它并查看控制台即可:

<script>
  window.customElements.define(
    'elmt-container',
    class extends HTMLElement {
      observeMutations() {
        const callback = (mutationList, observer) => {
          for (const mutation of mutationList) {
            if (mutation.type === 'childList') {
              if (mutation.addedNodes) {
                if (mutation.addedNodes[0] instanceof HTMLElement) {
                  console.log('A child node has been added:')
                  console.log(mutation.addedNodes[0])
                }
              }
            }
          }
        }
        const observer = new MutationObserver(callback)
        this.observer = observer
        const config = { childList: true, subtree: true }
        observer.observe(this, config)
      }
      disconnectedCallback() {
        const observer = this.observer
        observer.disconnect()
      }
      constructor() {
        super()
        this.observeMutations()
      }
    }
  )
  window.customElements.define('elmt-test', class extends HTMLElement {})
</script>

<elmt-container>
  <div>1</div>
  <elmt-test>2</elmt-test>
  <div><span>3</span></div>
</elmt-container>


0
投票

这个问题的解决办法非常简单。只需等待 HTML 被解析,然后再调用 customElements.register 即可。当必须在 connectCallback 中处理 HTML 子级时,我通常使用的方案:

class MyComponent extends HTMLElement{ static { globalThis.addEventListener('load', ()=>{customElements.define('my-component', MyComponent)}); } } 

© www.soinside.com 2019 - 2024. All rights reserved.