如何在调用任何构造函数之前定义所有自定义元素?

How to define all custom elements before any constructors are called?

我有两个自定义元素:OneTwoOne 有一个功能。 Two 具有 One 的 child 并尝试在其构造函数中调用该函数。它说如果我在 One 之前在 Two 上调用 customElements.define(),则该函数不存在。但是,如果我在 Two 之前定义 One,它就可以正常工作。

在我的实际项目中,我无法控制它们的定义顺序,默认情况下它们的定义顺序是错误的。

我尝试调用 connectedCallback() 中的函数,但这也失败了。

具体什么时候调用构造函数?

有什么方法可以确保它们在调用任何构造函数之前都已定义?

class One extends HTMLElement {
  constructor() {
    super()
    console.log('one constructor')
  }
  
  myFunc() {
    console.log('it worked!')
  }
}

class Two extends HTMLElement {
  constructor() {
    super()
    console.log('two constructor')
    
    this.innerHTML = '<my-one></my-one>'
    this.myOne = document.querySelector('my-one')
    
    // this part fails
    this.myOne.myFunc()
  }
  
  connectedCallback() {
    // this also fails
    this.myOne.myFunc()
  }
}

// this works
// customElements.define("my-one", One)
// customElements.define("my-two", Two)

// this breaks
customElements.define("my-two", Two)
customElements.define("my-one", One)
<my-two></my-two>

这是关于 Web 组件的生命周期以及标签何时从 HTMLUnknownElement 升级到您的实际组件

分两步定义组件。

1) class 的定义 2) 调用 customElements.define

是的,这两个可以在一起:

customElements.define('dog-food', class extends HTMLElement{});

但是 class 定义仍然发生在 customElements.define 被调用之前。

只有在发生以下两种情况时,元素才会升级为自定义元素:

1) 自定义元素必须使用 customElements.define 和 2) 自定义元素必须 1) 使用 document.createElementnew MyElement 实例化 2) 被添加到 DOM 树

此示例将元素放入 DOM,但未定义 1 秒。

我在定义之前显示构造函数,然后在定义之后再次显示它。

class One extends HTMLElement {
  constructor() {
    super();
    console.log('one constructor');
  }

  connectedCallback() {
    this.innerHTML = "I have upgraded";
  }
}

let el = document.querySelector('my-one');

setTimeout(() => {
  customElements.define("my-one", One);
  console.log('after: ', el.constructor.toString().substr(0,30));
}, 1000);

console.log('before: ', el.constructor);
  
<my-one>I am just a simple element</my-one>

在您的代码中,您使用 innerHTML 到 "load" <my-one>。但是由于 <my-two> 可能不会 "really" 在调用构造函数时 DOM 中,因此 innerHTML 不会在 DOM 中,因此, <my-one> 还不会升级。

您可以做的一件事是等待 <my-two> 组件真正放入 DOM,方法是等待更改 connectedCallback 函数中的 innerHTML

class One extends HTMLElement {
  constructor() {
    super();
    console.log('one constructor');
  }
  
  myFunc() {
    console.log('it worked!');
  }
}

class Two extends HTMLElement {
  constructor() {
    super();
    console.log('two constructor');
  }
  
  connectedCallback() {
    this.innerHTML = '<my-one></my-one>';
    setTimeout(() => {
      this.myOne = document.querySelector('my-one');
      this.myOne.myFunc();
    });
  }
}

customElements.define("my-two", Two)
customElements.define("my-one", One)
<my-two></my-two>

您会注意到我仍然必须将对 <my-one> 中函数的调用放入 setTimeout 调用中。这是因为在您的 connectedCallback 函数存在之前,<my-one> 元素无法升级。升级需要一个机会运行,它不会运行在你的函数中间。

另一种方法是直接调用 <my-one> 的构造函数:

class One extends HTMLElement {
  constructor() {
    super();
    console.log('one constructor');
  }

  connectedCallback() {
    this.innerHTML = "one";
  }

  myFunc() {
    console.log('it worked!');
  }
}

class Two extends HTMLElement {
  constructor() {
    super();
    console.log('two constructor');
  }
  
  connectedCallback() {
    customElements.whenDefined('my-one').then(() => {
      this.myOne = document.createElement('my-one');
      this.append(this.myOne);
      this.myOne.myFunc();
    });
  }
}

customElements.define("my-two", Two);
customElements.define("my-one", One);
<my-two></my-two>

在这里您会注意到我必须添加对 customElements.whenDefined 的调用。这将等到 <my-one> 在尝试实例化之前实际定义。一旦它被定义,你就可以创建它并立即调用成员函数。

最后一件事。在 Web 组件的构造函数中,有关于您应该做什么和不应该做什么的规则。它们在这里定义 https://w3c.github.io/webcomponents/spec/custom/#custom-element-conformance

但我要指出的一件事是,您不应该触及或更改构造函数中的任何属性或子元素。主要是因为构建Web Component 时没有属性或任何子元素。这些都是事后更改和添加的。