我引用了一个元素,该元素在某些时候会升级为自定义元素。我如何等待它升级?
例如,假设el
是参考。如果它假设为此目的附加了一个承诺,代码可能类似于
await el.upgradePromise
// do something after it has been upgraded.
那当然不存在,但描述了我想做的事情。也许没有投票就没有办法做到这一点?如果我使用轮询,我会轮询什么(假设我没有引用它应该升级到的类构造函数)。也许我可以轮询el.constructor
并等待它不是HTMLElement
,或等待它不是HTMLUnknownElement
?
编辑:对于后台,我有一些代码,如下所示,其中使用setTimeout是一个hack,以便代码工作。第一个console.log
输出为false,而超时中的一个输出为真。
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)
}
}
其中OtherElement
是对某个自定义元素类的引用,该类将在某个时刻进行注册。请注意,我在我的案例中使用了Chrome v0 document.registerElement
。需要超时,因为如果首先注册SomeElement
,如下面的代码所示,那么OtherElement
将不会被注册,因此如果SomeElement
元素的子元素是预期的,那么OtherElement
的实例,然后在下次升级这些元素之前不会出现这种情况。
document.registerElement('some-el', SomeElement)
document.registerElement('other-el', OtherElement)
理想情况下,这样的超时是不可取的,因为如果升级需要花费更长的时间(由于某些未知原因可能取决于浏览器的实现),那么超时黑客也将失败。
我想要一种绝对的方式来等待升级而不会出现可能的故障,并且如果可能的话不进行轮询。也许它需要在一段时间后取消?
编辑:理想的解决方案将允许我们等待任何第三方自定义元素的升级,而无需在运行时之前修改这些元素,也无需在运行时进行修补。
编辑:通过观察Chrome的v0行为,似乎第一次调用document.registerElement('some-el', SomeElement)
会导致升级这些元素并将其attachedCallback
方法解析为之前 OtherElement
的注册,因此孩子的类型不正确。然后,通过推迟逻辑,我可以在孩子们升级到OtherElement
类型后运行逻辑。
编辑:这是一个显示the problem的jsfiddle,这里是一个显示the timeout hack solution的jsfiddle。两者都是使用Chrome Canary中的Custom Elements v1 API编写的,并且无法在其他浏览器中使用,但问题是使用Chrome稳定版的自定义元素v0 API document.registerElement
和{{1而不是attachedCallback
和customElements.define
。 (请参阅两个小提琴中的控制台输出。)
答案 0 :(得分:3)
您可以使用window.customElements.whenDefined("my-element")
返回Promise
,您可以使用window.customElements.whenDefined('my-element').then(() => {
// do something after element is upgraded
})
来确定元素何时升级。
{

答案 1 :(得分:1)
由于html和javascript解析的sync
顺序,您只需要等待和插入元素。
第一个测试用例 - 插入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>
第二个测试用例 - 定义的元素然后插入:
// 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 )
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 )
&#13;
<el-one id="E1">
<el-two id="E2">
</el-two>
</el-one>
&#13;
第三个测试用例 - 未知元素名称
如果您不知道内部元素的名称,可以解析外部元素的内容,并在每个发现的自定义元素上使用whenDefined()
。
// TEST CASE 3
class ElementOne extends HTMLElement {
connectedCallback() {
var customs = []
for (var element of this.children) {
if (!customs.find(name => name == element.localName) &&
element.localName.indexOf('-') > -1)
customs.push(element.localName)
}
customs.forEach(name => customElements.whenDefined(name).then(() =>
console.log(name + ' expected to be true:', this.children[0] instanceof customElements.get(name))
))
}
}
class ElementTwo extends HTMLElement {}
customElements.define('el-one', ElementOne)
customElements.define('el-two', ElementTwo)
&#13;
<el-one>
<el-two>
</el-two>
</el-one>
&#13;
注意如果您必须等待升级不同的自定义元素,那么您将获得Promise.all()
分辨率。您可能还希望执行更详细的(递归)解析。
答案 2 :(得分:0)
@trusktr如果我理解你的问题,这绝对有可能通过创造性地使用:defined
伪选择器,MutationObserver
&amp;您已经提到的自定义元素方法
const o = new MutationObserver(mutationRecords => {
const shouldCheck = mutationRecords.some(mutationRecord => mutationRecord.type === 'childList' && mutationRecord.addedNodes.length)
const addedNodes = mutationRecords.reduce((aN, mutationRecord) => aN.concat(...mutationRecord.addedNodes), [])
const undefinedNodes = document.querySelectorAll(':not(:defined)')
if (shouldCheck) {
console.info(undefinedNodes, addedNodes);
[...undefinedNodes].forEach(n => customElements.whenDefined(n.localName).then(() => console.info(`${n.localName} defined`)))
}
})
o.observe(document.body, { attributes: true, childList: true })
class FooDoozzz extends HTMLElement { connectedCallback () { this.textContent = 'FUUUUUCK' } }
// Will tell you that a "foo-doozzz" element that's undefined has been added
document.body.appendChild(document.createElement('foo-doozzz'))
// Will define "foo-doozzz", and you event fires telling you it was defined an there are no longer any undefined elements
customElements.define('foo-doozzz', FooDoozzz)
// You'll see an event fired telling you an element was added, but there are no undefined elements still
document.body.appendChild(document.createElement('foo-doozzz'))
答案 3 :(得分:0)
再次提出该问题,因为最初的问题尚未得到解决,并且这里可能存在规格增强的情况。
基本上,我的问题与OP相同:在处理DOM元素的框架代码中,我需要检测一个未定义的对象并将其处理推迟到定义后。
james_womack(及其他地方)提出的 element.matches(':defined')
是一个很好的开始,因为它无需确定给定元素是否是自定义/自定义的,这本身并不有趣。
此方法解决了自定义元素的问题:customElements.whenDefined(element.localName)
。
这对于自定义内置元素来说是不够的,因为本地名称将只是标准节点名称。
在元素上保留is
属性的值可以解决此问题,但今天的规范并不需要。
因此,在执行以下代码时:
let element = document.createElement('input', {is: 'custom-input'});
is
属性丢失。
顺便说一句,当有效执行与以下操作相同的操作时,is
属性将保留:
document.body.innerHTML += '<input is="custom-input"/>'
。
恕我直言,is
属性也应该在程序设计创建流程中保留 ,从而提供一致的API行为并提供等待定义自定义内置元素的功能。 / p>
附录: