如何在连接所有子自定义元素时使用'connectedCallback'

Ilm*_*ont 15 html javascript web-component native-web-component

我正在使用Web Components v1.

假设有两个自定义元素:

家长element.html

<template id="parent-element">
    <child-element></child-element>
</template>
Run Code Online (Sandbox Code Playgroud)

儿童element.html

<template id="child-element">
<!-- some markup here -->
</template>
Run Code Online (Sandbox Code Playgroud)

我试图在连接时使用connectedCallbackin parent-element初始化整个父/子DOM结构,这需要与定义的方法进行交互child-element.

但是,似乎child-element没有正确定义当时connectedCallback被解雇customElement:

家长element.js

class parent_element extends HTMLElement {
    connectedCallback() {
        //shadow root created from template in constructor previously
        var el = this.shadow_root.querySelector("child-element");
        el.my_method();
    }
}
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为它el是一个HTMLElement而不是child-element预期的.

parent-element的模板中的所有子自定义元素都已正确附加后,我需要回调.

这个问题的解决方案似乎不起作用; this.parentElementnull里面child-element connectedCallback().

ilmiont

Emi*_*ier 10

在 ShadowDOM 模板中使用插槽元素。

以某种方式构建您的自定义元素,以便它们可以存在于任何上下文中,例如作为子元素或父元素,而与其他自定义元素没有任何依赖关系。这种方法将为您提供模块化设计,您可以在任何情况下使用自定义元素。

但是您仍然希望在存在子元素时执行某些操作,例如选择它们或调用子元素的方法。

插槽元素

为了解决这个问题,<slot>引入了元素。使用插槽元素,您可以在 ShadowDOM 模板中创建占位符。这些占位符可以通过简单地将一个元素作为 DOM 中的子元素放置在您的自定义元素中来使用。然后子元素将被放置在<slot>放置元素的位置内。

但是你怎么知道占位符是否已经填充了元素?

插槽元素可以侦听名为 的唯一事件slotchange。每当一个元素(或多个元素)放置在元素的位置上时,就会触发这个slot

在事件的侦听器中,您可以使用HTMLSlotElement.assignedNodes()HTMLSlotElement.assignedElements()方法访问占位符中的所有元素。这些返回一个数组,其中的元素放置在slot.

现在您可以等待孩子被放置在插槽内,并对在场的孩子做一些事情。

这种方式允许您只操作 DOM,而让 ShadowDOM 独自完成它的工作。就像处理常规 HTML 元素一样。

事件是否会等待所有子元素连接?

是的,在调用自定义元素的slotchange所有connectedCallback方法后会触发该事件。这意味着在收听赛事时不会出现赛车状况或缺少设置。

class ParentElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `
      <h2>Parent Element</h2>
      <slot></slot>
    `;
    console.log("I'm a parent and have slots.");
    
    // Select the slot element from the ShadowDOM..
    const slot = this.shadowRoot.querySelector('slot');
    
    // ..and listen for the slotchange event.
    slot.addEventListener('slotchange', (event) => {
      // Get the elements assigned to the slot..
      const children = event.target.assignedElements();
      
      // ..loop over them and call their methods.
      children.forEach(child => {
        if (child.tagName.toLowerCase() === 'child-element') {
          child.shout()
        }
      });
    });
  }
  
  connectedCallback() {
    console.log("I'm a parent and am now connected");
  }
}

customElements.define('parent-element', ParentElement);

class ChildElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `
      <h3>Child Element</h3>
    `;
  }
  
  connectedCallback() {
    console.log("I'm a child and am now connected.");
  }

  shout() {
    console.log("I'm a child and placed inside a slot.");
  }

}

customElements.define('child-element', ChildElement);
Run Code Online (Sandbox Code Playgroud)
<parent-element>
  <child-element></child-element>
  <child-element></child-element>
  <child-element></child-element>
</parent-element>
Run Code Online (Sandbox Code Playgroud)


Int*_*lia 6

connectedCallback升级其任何自定义元素子元素之前,第一次调用 It存在计时问题。被调用<child-element>时只是一个 HTMLElement connectedCallback

要获得升级后的子元素,您需要在超时时间内完成。

运行下面的代码并观察控制台输出。当我们尝试调用孩子的方法时,它失败了。同样,这是因为 Web 组件的创建方式。和时间的时间connectedCallback被调用。

但是,在对setTimeout孩子的方法的调用中起作用。这是因为您留出了时间让子元素升级为您的自定义元素。

如果你问我,有点傻。我希望在所有孩子都升级后调用另一个函数。但我们用我们拥有的东西工作。

class ParentElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = '<h2>Parent Element</h2><child-element></child-element>';
  }
  
  connectedCallback() {
    let el = this.shadowRoot.querySelector("child-element");
    console.log('connectedCallback', el);
    try {
      el.childMethod();
    }
    catch(ex) {
      console.error('Child element not there yet.', ex.message);
    }
    setTimeout(() => {
      let el = this.shadowRoot.querySelector("child-element");
      console.log('setTimeout', el);
      el.childMethod();
    });
  }
}

customElements.define('parent-element', ParentElement);


class ChildElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = '<h3>Child Element</h3>';
  }

  childMethod() {
    console.info('In Child method');
  }
}

customElements.define('child-element', ChildElement);
Run Code Online (Sandbox Code Playgroud)
<parent-element></parent-element>
Run Code Online (Sandbox Code Playgroud)


Ilm*_*ont 5

经过更多的工作,我有了一个解决方案。

当然this.parentElement在子元素中不起作用;它位于影子 DOM 的根部!

我目前的解决方案适合我的具体场景,如下:

父元素.js

init() {
    //Code to run on initialisation goes here
    this.shadow_root.querySelector("child-element").my_method();
}
Run Code Online (Sandbox Code Playgroud)

子元素.js

connectedCallback() {
    this.getRootNode().host.init();
}
Run Code Online (Sandbox Code Playgroud)

因此,在子元素中,我们获取根节点(模板影子 DOM),然后获取其宿主、父元素,并调用init(...),此时父元素可以访问子元素,并且它已完全定义。

由于多种原因,该解决方案并不理想,因此我不会将其标记为已接受。

1)如果有多个子级需要等待,或者嵌套更深,那么协调回调将会变得更加复杂。

2)我担心 的影响child-element,如果我想以独立的方式使用这个元素(即在其他地方,完全独立于嵌套在 中parent-element),我将必须修改它以显式检查是否getRootNode().host是 的实例parent-element

所以这个解决方案目前有效,但感觉很糟糕,我认为当整个 DOM 结构(包括其影子 DOM 中的嵌套自定义元素)初始化时,需要有一个回调在父级上触发。

  • 恐怕 Netscape Navigator 或 IE 4 等现代浏览器不支持 `parentElement`。不要使用。 (4认同)