为什么我不能调用 WebComponent 的方法?

Why I can not call method of WebComponent?

我想知道如果我通过 .append 附加此组件而不是在模板中使用标签名称,为什么我不能调用在 web-component 中定义的方法。下面我提供几个例子。一个不工作(抛出错误)。我想知道为什么第一个示例会抛出此错误。

示例 1

const templateB = document.createElement('template');
templateB.innerHTML = `
<h1>ComponentB</h1>
`

class ComponentB extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"});
    this.shadowRoot.append(templateB.content.cloneNode(true));
  }

  hello() {
    console.log('Hello');
  }
}

customElements.define('component-b', ComponentB);

const templateA = document.createElement('template');
templateA.innerHTML = `
<div>
<component-b></component-b>
</div>
`;

class ComponentA extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"});
    this.shadowRoot.append(templateA.content.cloneNode(true));
    this.componentB = this.shadowRoot.querySelector('component-b');
    console.log(this.componentB instanceof ComponentB);
    this.componentB.hello();
  }

}

customElements.define('component-a', ComponentA);

document.body.append(new ComponentA());

在这个例子中,我在我的 js 文件中创建了一个网络组件,然后直接将它附加到文档中。在这种情况下,我得到一个错误,指出 .hello 在我的 ComponentB 中不存在。更重要的是,我使用 .querySelectorComponentB 实例的引用不是 ComponentB.

的实例

示例 2

const templateB = document.createElement('template');
templateB.innerHTML = `
<h1>ComponentB</h1>
`

class ComponentB extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"});
    this.shadowRoot.append(templateB.content.cloneNode(true));
  }

  hello() {
    console.log('Hello');
  }
}

customElements.define('component-b', ComponentB);

const templateA = document.createElement('template');
templateA.innerHTML = `
<div>
<component-b></component-b>
</div>
`;

class ComponentA extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"});
    this.shadowRoot.append(templateA.content.cloneNode(true));
    this.componentB = this.shadowRoot.querySelector('component-b');
    console.log(this.componentB instanceof ComponentB);
    this.componentB.hello();
  }

}

customElements.define('component-a', ComponentA);
<component-a></component-a>

在这个例子中,我将一个网络组件直接添加到 html 文件。在这种情况下,我没有收到错误,并且我使用 .querySelector 获得的对 ComponentB 实例的引用是 ComponentB.

的实例

示例 3

const templateB = `
<h1>ComponentB</h1>
`;

class ComponentB extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"});
    this.shadowRoot.innerHTML = templateB;
  }

  hello() {
    console.log('Hello');
  }
}

customElements.define('component-b', ComponentB);

const templateA = `
<div>
<component-b></component-b>
</div>
`;

class ComponentA extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"});
    this.shadowRoot.innerHTML = templateA;
    this.componentB = this.shadowRoot.querySelector('component-b');
    console.log(this.componentB instanceof ComponentB);
    this.componentB.hello();
  }

}

customElements.define('component-a', ComponentA);

document.body.append(new ComponentA());

在这个例子中,我在我的 js 文件中创建了一个网络组件,然后直接将它附加到文档中。在这种情况下,我没有收到错误,并且我使用 .querySelector 获得的对 ComponentB 实例的引用是 ComponentB 的实例。 示例 1示例 3 之间的唯一区别是我在这里使用 .innerHTML 而不是深度克隆模板。

从我的角度来看,示例 1 是正确的,应该可以工作。谁能向我解释为什么我错了以及为什么它不起作用?也许你也可以提供一个解决方案,我如何在 js 文件中使用 <template> + .cloneNode 来访问我以这种方式创建的网络组件的方法?

https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/upgrade

我首先注意到,如果我在尝试调用 componentB 方法之前添加一个小的 setTimeout,那么示例 1 工作正常。通过进一步的调整,我能够让示例 1 大部分按原样工作,唯一的变化是在尝试访问其任何方法之前,我在组件 B 引用上调用了上面文档 link 中描述的方法。

 customElements.upgrade(this.componentB);

我会尝试解释,但老实说,Web 组件生命周期挂钩的细微差别和确切时间让我仍然有些困惑。当第一次尝试访问其方法时,这显然是组件未在自定义元素注册表中完全注册的问题,我只是不确定为什么其他 2 个示例按预期工作,而第一个示例需要显式'upgrading'.

简单说明:

.innerHTML同步

因此<div><component-b></component-b></div>在构造Component-A时立即解析

.append with TemplatesA-synchronous,它将创建Component A shadowDOM 中的 HTML,但将解析留给稍后

我清理了您的代码以仅显示相关部分,并添加了 console.log 以显示 组件 B 何时构建

您可以玩组件 A 中的 append/append/innerHTML

(复杂解释)深度视频:https://www.youtube.com/watch?v=8aGhZQkoFbQ

注意:您实际上应该尝试避免 this.componentB.hello 风格编码,
因为它在组件 之间创建了紧密耦合。即使 B 尚不存在,组件 A 也应该可以工作。是的,这需要更复杂的编码(事件、承诺等)。如果您有紧密耦合的组件,您应该考虑将它们设为 1 个组件。

<script>
  customElements.define('component-b', class extends HTMLElement {
    constructor() {
      console.log("constructor B");
      super().attachShadow({mode: "open"}).innerHTML = "<h1>ComponentB</h1>";
    }

    hello() {
      console.log('Hello');
    }
  });

  const templateA = document.createElement('template');
  templateA.innerHTML = `<div><component-b></component-b></div>`;

  customElements.define('component-a', class extends HTMLElement {
    constructor() {
      console.log("constructor A");
      super().attachShadow({mode: "open"})
      .append(templateA.content.cloneNode(true));
      //.append(document.createElement("component-b"));
      //.innerHTML = "<div><component-b></component-b></div>";
      this.componentB = this.shadowRoot.querySelector('component-b');
      console.assert(this.componentB.hello,"component B not defined yet");
    }
  });

  document.body.append(document.createElement("component-a"));
</script>