如何等待“自定义元素”引用被“升级”?

tru*_*ktr 4 javascript dom web-component polymer custom-element

我有一个元素的引用,该元素有时会升级为自定义元素。我如何等待它升级?

例如,假设el是参考。如果假设为此目的附加了承诺,则代码可能类似于

await el.upgradePromise
// do something after it has been upgraded.
Run Code Online (Sandbox Code Playgroud)

那当然不存在,但是描述了我想做什么。也许没有轮询就没有办法吗?如果使用轮询,我将轮询什么(假设我没有对应该升级到的类构造函数的引用)。也许我可以轮询el.constructor并等待其不属于HTMLElement,或等待它不被HTMLUnknownElement

编辑:对于背景,我有一些类似以下的代码,其中使用setTimeout是为了使代码正常工作的技巧。第一个console.log输出false,而超时中的一个输出true。

import OtherElement from './OtherElement'

class SomeElement extends HTMLElement {
    attachedCallback() {
        console.log(this.children[0] instanceof OtherElement) // false

        setTimeout(() => {
            console.log(this.children[0] instanceof OtherElement) // true
        }, 0)
    }
}
Run Code Online (Sandbox Code Playgroud)

在哪里OtherElement引用将在某个时候注册的Custom Element类。请注意,我使用的是Chrome v0 document.registerElement。需要超时,因为如果SomeElement首先按以下代码OtherElement注册,则将尚未注册,因此,因此如果SomeElement元素的子级是的实例OtherElement,那么在升级这些元素之前不会如此下一个。

document.registerElement('some-el', SomeElement)
document.registerElement('other-el', OtherElement)
Run Code Online (Sandbox Code Playgroud)

理想情况下,这样的超时是不希望的,因为如果升级花费了更长的时间(由于某些未知原因,可能取决于浏览器的实现),那么超时破解也将失败。

我想要一种绝对的方式来等待升级,而不会出现故障,并且如果可能的话也不会进行轮询。也许过一段时间也需要取消吗?

编辑:理想的解决方案将使我们能够等待任何第三方自定义元素的升级,而无需在运行前修改这些元素,也不必在运行时进行猴子补丁。

编辑:从观察Chrome的v0行为来看,似乎第一个调用document.registerElement('some-el', SomeElement)导致那些元素升级,并且在注册之前attachedCallback会激发它们的方法,因此子元素将不是正确的类型。然后,通过延迟逻辑,我可以在子级也升级为type后运行逻辑。OtherElementOtherElement

编辑:这是一个显示问题的jsfiddle,这是一个显示超时hack解决方案的jsfiddle 。两者都是使用Chrome Canary中的Custom Elements v1 API编写的,在其他浏览器中均无法使用,但是使用Chrome Stable的Custom Elements v0 API和document.registerElementattachedCallback而不是customElements.define和时出现的问题相同connectedCallback。(请参阅两个小提琴中的控制台输出。)

小智 5

您可以使用window.customElements.whenDefined("my-element")which返回a Promise,可以用来确定何时升级元素。

window.customElements.whenDefined('my-element').then(() => {
  // do something after element is upgraded
})
Run Code Online (Sandbox Code Playgroud)

  • 该解决方案不起作用,因为作为库作者,我不知道最终用户选择的元素名称,因此我不知道要传递到“whenDefined”调用中的内容。这仅对定义元素名称的人有用。我正在创建一个 WebComponent 基类,它不知道子类将分配给什么名称。这是一个丑陋的问题,我想知道我是否做错了。也许我需要让子类作者在类属性或其他东西上定义元素(或实例)的名称。 (2认同)

Sup*_*arp 2

由于synchtml 和 javascript 解析的顺序,您只需等待元素被定义插入。

第一个测试用例 - 插入 HTML,然后定义元素:

<el-one id="E1">
  <el-two id="E2">
  </el-two>
</el-one>
<script>
  // TEST CASE 1: Register elements AFTER instances are already in DOM but not upgraded:
  customElements.define('el-one', ElementOne)
  customElements.define('el-two', ElementTwo)
  // END TEST CASE 1
  console.assert( E1 instanceof ElementOne )
  console.assert( E2 instanceof ElementTwo )
</script>
Run Code Online (Sandbox Code Playgroud)

第二个测试用例 - 定义然后插入的元素:

// TEST CASE 2: register elements THEN add new insances to DOM:
customElements.define('el-three', ElementThree)
customElements.define('el-four', ElementFour)
var four = document.createElement('el-four')
var three = document.createElement('el-three')
three.appendChild(four)
document.body.appendChild(three)
// END TEST CASE 2
console.assert( three instanceof ElementThree )
console.assert( four instanceof ElementFour )
Run Code Online (Sandbox Code Playgroud)

<el-one id="E1">
  <el-two id="E2">
  </el-two>
</el-one>
<script>
  // TEST CASE 1: Register elements AFTER instances are already in DOM but not upgraded:
  customElements.define('el-one', ElementOne)
  customElements.define('el-two', ElementTwo)
  // END TEST CASE 1
  console.assert( E1 instanceof ElementOne )
  console.assert( E2 instanceof ElementTwo )
</script>
Run Code Online (Sandbox Code Playgroud)
// TEST CASE 2: register elements THEN add new insances to DOM:
customElements.define('el-three', ElementThree)
customElements.define('el-four', ElementFour)
var four = document.createElement('el-four')
var three = document.createElement('el-three')
three.appendChild(four)
document.body.appendChild(three)
// END TEST CASE 2
console.assert( three instanceof ElementThree )
console.assert( four instanceof ElementFour )
Run Code Online (Sandbox Code Playgroud)

第三个测试用例 - 未知元素名称

如果您不知道内部元素的名称是什么,您可以解析外部元素的内容并whenDefined()在每个发现的自定义元素上使用。

class ElementZero extends HTMLElement {
    connectedCallback() {
        console.log( '%s connected', this.localName )
    }
}

class ElementOne extends ElementZero { }
class ElementTwo extends ElementZero { }

// TEST CASE 1: Register elements AFTER instances are already in DOM but not upgraded:
customElements.define('el-one', ElementOne)
customElements.define('el-two', ElementTwo)
// END TEST CASE 1
console.info( 'E1 and E2 upgraded:', E1 instanceof ElementOne && E2 instanceof ElementTwo )


class ElementThree extends ElementZero { }
class ElementFour extends ElementZero { }

// TEST CASE 2: register elements THEN add new insances to DOM:
customElements.define('el-three', ElementThree)
customElements.define('el-four', ElementFour)
const E4 = document.createElement('el-four')
const E3 = document.createElement('el-three')
E3.appendChild(E4)
document.body.appendChild(E3)
// END TEST CASE 2
console.info( 'E3 and E4 upgraded:', E3 instanceof ElementThree && E4 instanceof ElementFour )
Run Code Online (Sandbox Code Playgroud)
<el-one id="E1">
  <el-two id="E2">
  </el-two>
</el-one>
Run Code Online (Sandbox Code Playgroud)

注意如果您必须等待不同的自定义元素升级,您将寻求解决Promise.all()方案。您可能还想执行更详细的(递归)解析。