自定义元素中Getter与Setter失效问题求助
WebComponents组件组合中自定义输入框getter/setter失效问题解决
问题描述
构建嵌套WebComponent结构时遇到异常:
- 自定义列表
my-custom-list包含多个自定义项my-custom-item,每个项内有自定义输入框my-custom-input my-custom-input通过get/set的value访问器同步内部原生input的值,单独使用或直接在页面中使用my-custom-item都正常- 但通过
my-custom-list的items属性批量创建my-custom-item时,my-custom-input的访问器失效:设置value不会同步到原生input,原生input修改也不会反映到value属性,仿佛退化为普通属性
原因分析
问题出在模板创建与自定义元素定义的顺序:
当创建customItemTemplate并插入<my-custom-input>标签时,my-custom-input组件还未被定义,浏览器会将其视为HTMLUnknownElement。后续即使定义了组件,克隆这个模板生成的元素不会自动升级为自定义元素,因此无法识别我们定义的getter/setter。而页面中直接书写的<my-custom-input>会在组件定义后被浏览器自动升级,所以能正常工作。
解决方案
调整代码顺序,先定义所有依赖的自定义组件,再创建包含它们的模板。具体来说,先定义my-custom-input,再创建customItemTemplate;同理,customItemTemplate创建完成后再定义my-custom-item,确保模板中的自定义元素在创建时已经被定义。
修改后的完整代码
HTML部分
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Web Component</title> </head> <body> <h3>Custom input, works fine</h3> <my-custom-input id="my-custom-input" label="Custom input"></my-custom-input> <h3>Custom item, also works fine</h3> <my-custom-item id="my-custom-item"></my-custom-item> <h3>Inside custom list, now works</h3> <my-custom-list id="my-list"></my-custom-list> </body> </html>
JavaScript部分
// 1. 先定义my-custom-input组件 const customInputTemplate = document.createElement("template"); customInputTemplate.innerHTML = ` <label id="label" for="input"></label> <input id="input" type="text" /> `; window.customElements.define("my-custom-input", class MyCustomInput extends HTMLElement { static get observedAttributes() { return ["label"]; } constructor() { super(); this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(customInputTemplate.content.cloneNode(true)); this.labelElemenet = this.shadowRoot.getElementById("label"); this.inputElement = this.shadowRoot.getElementById("input"); } get value() { return this.inputElement.value; } set value(newValue) { this.inputElement.value = newValue; } attributeChangedCallback(name, oldValue, newValue) { if (name === "label") { this.labelElemenet.innerText = newValue; } } }); // 2. 再创建包含my-custom-input的模板,然后定义my-custom-item组件 const customItemTemplate = document.createElement("template"); customItemTemplate.innerHTML = ` <li> <my-custom-input id="custom-input" label="Item :"></my-custom-input> </li> `; window.customElements.define("my-custom-item", class MyCustomItem extends HTMLElement { constructor() { super(); this._item = undefined; this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(customItemTemplate.content.cloneNode(true)); this.customInputElement = this.shadowRoot.getElementById("custom-input"); } get item() { return this._item; } set item(newItem) { this._item = newItem; this.render(); } render() { if (this.customInputElement.value !== this.item.name) { this.customInputElement.value = this.item.name; } } }); // 3. 最后定义my-custom-list组件 const customListTemplate = document.createElement("template"); customListTemplate.innerHTML = ` <ul> <slot></slot> </ul> `; window.customElements.define("my-custom-list", class MyCustomList extends HTMLElement { constructor() { super(); this._items = []; this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(customListTemplate.content.cloneNode(true)); } get items() { return this._items; } set items(newItems) { this._items = newItems; this.render(); } render() { this.replaceChildren(...this.items.map(item => { const itemElement = document.createElement("my-custom-item"); itemElement.item = item; return itemElement; })); } }); // 测试代码 const customInputElement = document.getElementById("my-custom-input"); customInputElement.value = "Some value for my custom input"; const customItemElement = document.getElementById("my-custom-item"); customItemElement.item = { id: "Z", name: "Some name for my custom item" }; const listElement = document.getElementById("my-list"); listElement.items = [ { id: "A", name: "Item A" }, { id: "B", name: "Item B" }, { id: "C", name: "Item C" } ]; // 验证修复效果 const firstItem = listElement.querySelector("my-custom-item"); const customInputOfFirstItem = firstItem.shadowRoot.querySelector("my-custom-input"); console.log("customInputOfFirstItem.value = ", customInputOfFirstItem.value); // 输出"Item A" customInputOfFirstItem.value = "Updated item A"; console.log("After forcing value, customInputOfFirstItem.value = ", customInputOfFirstItem.value); // 输出"Updated item A" const originalInput = customInputOfFirstItem.shadowRoot.getElementById("input"); console.log("Original input value = ", originalInput.value); // 输出"Updated item A" originalInput.value = "Forced inside value for item A"; console.log("After forcing inside value, customInputOfFirstItem.value = ", customInputOfFirstItem.value); // 输出"Forced inside value for item A"
内容的提问来源于stack exchange,提问作者Sylvain Desvê
相关产品推荐
相关产品推荐

