如何使用cloneNode传递自定义元素的状态?
解决自定义元素克隆时传递复杂状态的方案
这个问题太常见了——默认的cloneNode只会复制元素的DOM属性和子节点,完全不会管你存在实例里的私有属性(比如你的_text)。除了把状态存在DOM属性里,还有几个更灵活的方案,根据你的场景选就行:
1. 自定义克隆方法,手动复制状态
直接给自定义元素加个clone方法,在里面调用原生cloneNode后,手动把需要的私有属性复制过去。这种方式最直观,想复制什么就复制什么:
class MyP extends HTMLElement { constructor() { super(); this._text = ''; const shadow = this.attachShadow({ mode: 'open' }); this.p = document.createElement('p'); shadow.appendChild(this.p); } get text() { return this._text; } set text(text) { this._text = text; this.p.innerText = text; } // 自定义克隆方法,代替原生cloneNode clone(deep = true) { const clonedElement = super.cloneNode(deep); // 把原元素的私有状态复制给克隆体 clonedElement._text = this._text; // 同步shadow DOM里的内容 clonedElement.p.innerText = this._text; return clonedElement; } } customElements.define('my-p', MyP); // 使用的时候调用自定义的clone方法 const original = document.querySelector('my-p'); original.text = 'Hiding in the shadows'; document.body.append(original.clone(true));
2. 用observedAttributes绑定状态到DOM属性
如果不想改克隆的调用方式,那可以把私有状态和DOM属性做双向绑定,利用自定义元素的属性观察机制。这样cloneNode复制DOM属性后,克隆元素会自动把属性值同步到私有状态里:
class MyP extends HTMLElement { // 声明要观察的DOM属性 static get observedAttributes() { return ['data-text']; } constructor() { super(); this._text = ''; const shadow = this.attachShadow({ mode: 'open' }); this.p = document.createElement('p'); shadow.appendChild(this.p); } // 属性变化时自动同步状态 attributeChangedCallback(name, oldValue, newValue) { if (name === 'data-text') { this._text = newValue; this.p.innerText = newValue; } } get text() { return this._text; } set text(text) { // 设置属性,自动触发同步逻辑 this.setAttribute('data-text', text); } } customElements.define('my-p', MyP); // 原生cloneNode就能正常工作了 const original = document.querySelector('my-p'); original.text = 'Hiding in the shadows'; document.body.append(original.cloneNode(true));
这个方案完全符合自定义元素的标准行为,不需要额外处理克隆逻辑,适合状态是字符串、数字这类可以直接存在DOM属性里的场景。
3. 用ElementInternals存储复杂状态
如果你的状态是对象、数组这类复杂类型,DOM属性只能存字符串就不太方便了。这时候可以用ElementInternals来存储内部状态,它专门用来处理自定义元素的内部状态管理,虽然最初是为表单元素设计的,但用来存复杂状态也很合适:
class MyP extends HTMLElement { constructor() { super(); // 绑定ElementInternals this.internals = this.attachInternals(); this._text = ''; const shadow = this.attachShadow({ mode: 'open' }); this.p = document.createElement('p'); shadow.appendChild(this.p); } get text() { return this._text; } set text(text) { this._text = text; this.p.innerText = text; // 把状态存在internals里,支持复杂类型(对象/数组) this.internals.setFormValue(text); } clone(deep = true) { const clonedElement = super.cloneNode(deep); // 从internals里恢复状态 clonedElement._text = this.internals.formValue; clonedElement.p.innerText = clonedElement._text; return clonedElement; } } customElements.define('my-p', MyP); const original = document.querySelector('my-p'); original.text = 'Hiding in the shadows'; document.body.append(original.clone(true));
如果要存对象,只需要把对象序列化(比如JSON.stringify)后存在formValue里,克隆的时候再JSON.parse恢复就行。
总结一下:
- 简单状态(字符串/数字):优先用
observedAttributes方案,最省心,符合标准; - 不想依赖DOM属性:自定义
clone方法,灵活可控; - 复杂状态(对象/数组):用
ElementInternals来存储,避免DOM属性的字符串限制。
内容的提问来源于stack exchange,提问作者Roland Soós
相关产品推荐
相关产品推荐

