You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义元素中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ê

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 06:00:07