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

Web Component渲染元素删除异常:数组更新但页面未重渲染

Web Component 删除按钮不触发重新渲染的问题修复

核心问题分析

  • filter是纯函数,不会修改原数组,你当前只调用了filter但没将返回值赋值给原数组,导致window.selectedItems.items实际没有变化。
  • SelectedProducts的render方法用forEach循环每次覆盖innerHTML,最终页面只会显示最后一个选中项。
  • DeleteButton在构造函数中绑定点击事件时,组件还未挂载到DOM,按钮元素不存在,导致事件绑定失效。
  • SelectBtn里的name变量未定义,应该从元素的name属性获取值。

修复方案

1. 修正数组更新逻辑

在DeleteButton的handleDelete中,将filter返回的新数组赋值给原数组,确保数据源同步:

// 直接修改全局数据源
window.selectedItems.items = window.selectedItems.items.filter(item => item.id !== productId);

2. 修复批量渲染逻辑

SelectedProducts的render方法应该先拼接所有项的HTML,再一次性赋值,避免循环覆盖:

render() {
  // 拼接所有选中项的HTML
  const itemsHtml = this.items.map((item) => `
    <div style="display:flex; align-items: center; background-color:white; padding:15px; margin-bottom:8px;">
      <span>商品: ${item.name} | 数量: ${item.qty}</span>
      <delete-button>
        <button data-id="${item.id}" class="delete-btn">
          Delete me
        </button>
      </delete-button>
    </div>
  `).join('');
  this.innerHTML = itemsHtml;
}

3. 正确绑定事件到组件生命周期

DeleteButton的事件绑定应该放在connectedCallback中,确保组件挂载后再获取元素:

class DeleteButton extends HTMLElement {
  constructor() {
    super();
    this.selectedProducts = document.querySelector('selected-products');
  }

  connectedCallback() {
    // 组件挂载后再绑定事件
    this.querySelectorAll('button').forEach((button) => 
      button.addEventListener('click', this.handleDelete.bind(this))
    );
  }
}

4. 修正SelectBtn的变量未定义问题

从元素属性获取id和name,避免使用未定义变量:

handleSelect(e) {
  e.preventDefault();
  const itemId = this.getAttribute('id');
  const itemName = this.getAttribute('name');
  const itemObj = {
    id: itemId,
    name: itemName,
    qty: 1,
  };
  // ...后续逻辑不变
}

修改后的完整代码

JavaScript部分

class DeleteButton extends HTMLElement {
  constructor() {
    super();
    this.selectedProducts = document.querySelector('selected-products');
  }

  connectedCallback() {
    this.querySelectorAll('button').forEach((button) => 
      button.addEventListener('click', this.handleDelete.bind(this))
    );
  }

  handleDelete(e) {
    e.preventDefault();
    let productId = e.currentTarget.dataset.id;
    // 更新全局数据源
    window.selectedItems.items = window.selectedItems.items.filter(item => item.id !== productId);
    // 触发重新渲染
    this.selectedProducts.render();
  }
}
customElements.define('delete-button', DeleteButton);

class SelectedProducts extends HTMLElement {
  constructor() {
    super();
    this.items = window.selectedItems.items;
  }

  render() {
    const itemsHtml = this.items.map((item) => `
      <div style="display:flex; align-items: center; background-color:white; padding:15px; margin-bottom:8px;">
        <span style="margin-right:15px;">商品: ${item.name} | 数量: ${item.qty}</span>
        <delete-button>
          <button data-id="${item.id}" class="delete-btn">
            Delete me
          </button>
        </delete-button>
      </div>
    `).join('');
    this.innerHTML = itemsHtml;
  }
}
customElements.define('selected-products', SelectedProducts);

class ProductCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({
      mode: 'open',
    });

    this.shadowRoot.innerHTML =
      `<style>
      ::slotted(div){
        color: #4B5563; 
        font-weight: 900; 
        text-align: center; 
        font-size: 20px; 
      }
      </style>
      ` +
      ` <div style="background: white; margin-right: 15px; padding:10px;">
          <slot name="button"></slot>
          <slot name="img"></slot>
        </div>
      `;
  }
}

customElements.define('product-card', ProductCard);

class SelectBtn extends HTMLElement {
  constructor() {
    super();
    this.itemsPicked = document.querySelector('selected-products');
    this.attachShadow({
      mode: 'open',
    });
    this.shadowRoot.innerHTML = `
    <button
    aria-label="Select"
    type="button"
    class="pressed"
    data-addbtn="add-btn"
    style="padding:8px 16px; cursor:pointer;"
  >
   + 选择商品
  </button>
    
    `;

    this.shadowRoot
      .querySelectorAll('button')
      .forEach((button) => button.addEventListener('click', this.handleSelect.bind(this)));
  }

  handleSelect(e) {
    e.preventDefault();
    const itemId = this.getAttribute('id');
    const itemName = this.getAttribute('name');
    const itemObj = {
      id: itemId,
      name: itemName,
      qty: 1,
    };

    const item_stored = window.selectedItems.items.find((item) => item.id == itemObj.id);

    if (item_stored) {
      item_stored.qty++;
    } else {
      window.selectedItems.items.push(itemObj);
    }
    this.itemsPicked.render();
  }
}

customElements.define('select-button', SelectBtn);

HTML部分

<body>
    <div style="display: flex; justify-content: center; align-items: center; background: lightblue; padding: 20px">
      <product-card>
        <div slot="button">
          <select-button id="1" name="商品A"></select-button>
        </div>
        <div slot="img">
          <div style="height: 100px; width: 100px; background:#f0f0f0; display:flex; align-items:center; justify-content:center;">商品A</div>
        </div>
      </product-card>
      <product-card>
        <div slot="button">
          <select-button id="2" name="商品B"></select-button>
        </div>
        <div slot="img">
          <div style="height: 100px; width: 100px; background:#f0f0f0; display:flex; align-items:center; justify-content:center;">商品B</div>
        </div>
      </product-card>
      <div style="margin-left:20px;">
        <h3>已选商品</h3>
        <selected-products></selected-products>
      </div>
    </div>
    <script src="app.js"></script>
    <script>
      window.selectedItems = {
        items: []
      };
    </script>
  </body>

内容的提问来源于stack exchange,提问作者Mike Haslam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 14:27:24