原生JavaScript实现的Web Component在属性变更后如何触发重新渲染?
问题原因
- 模板渲染逻辑仅在组件挂载触发的
connectedCallback生命周期中执行了一次,后续数据变更不会自动同步到DOM attributeChangedCallback监听到属性变更后,仅更新了实例上的变量值,没有触发视图更新逻辑
修复方案
方案1:全量重绘(实现最简单)
抽离公共渲染方法,属性变更时主动触发重绘,每次重绘前清空旧的DOM内容,避免重复挂载:
class TimePanel extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }) } getTemplate() { const template = document.createElement('template'); template.innerHTML = ` <section class="general-panel"> <div class="inner-card work"> <div class="sub-panel"> <div class="flex-container"> <p class="card-title">${this.area}</p> <span class="options"> <img src="../../images/icon-ellipsis.svg" alt=""> </span> </div> <div class="flex-container"> <span class="time">${this.time}Hs</span> <span class="previous-date">last week - ${this.previousDate}Hs</span> </div> </div> </div> ` return template; } // 抽离公共渲染方法 render() { // 清空原有内容避免重复挂载 this.shadowRoot.innerHTML = ''; this.shadowRoot.append(this.getTemplate().content.cloneNode(true)) } connectedCallback() { this.render(); } static get observedAttributes() { return ['area','time','previous-date']; } attributeChangedCallback(name, old, newV) { // 新旧值一致时跳过更新,避免无效渲染 if(old === newV) return; if(name === 'area') { this.area = newV; } if(name === 'time') { this.time = newV; } if(name === 'previous-date') { this.previousDate = newV; } // 属性更新后触发重绘 this.render(); } } customElements.define('time-panel', TimePanel)
方案2:局部更新(性能更优)
仅更新发生变化的DOM节点,不需要重绘整个组件,适合结构复杂的组件:
class TimePanel extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }) // 缓存DOM节点引用 this.areaEl = null; this.timeEl = null; this.previousDateEl = null; } getTemplate() { const template = document.createElement('template'); template.innerHTML = ` <section class="general-panel"> <div class="inner-card work"> <div class="sub-panel"> <div class="flex-container"> <p class="card-title">${this.area}</p> <span class="options"> <img src="../../images/icon-ellipsis.svg" alt=""> </span> </div> <div class="flex-container"> <span class="time">${this.time}Hs</span> <span class="previous-date">last week - ${this.previousDate}Hs</span> </div> </div> </div> ` return template; } connectedCallback() { this.shadowRoot.append(this.getTemplate().content.cloneNode(true)) // 挂载时缓存需要更新的节点引用 this.areaEl = this.shadowRoot.querySelector('.card-title'); this.timeEl = this.shadowRoot.querySelector('.time'); this.previousDateEl = this.shadowRoot.querySelector('.previous-date'); } static get observedAttributes() { return ['area','time','previous-date']; } attributeChangedCallback(name, old, newV) { if(old === newV) return; // 直接更新对应节点的文本内容,无需重绘整个组件 if(name === 'area') { this.area = newV; this.areaEl?.textContent = newV; } if(name === 'time') { this.time = newV; this.timeEl?.textContent = `${newV}Hs`; } if(name === 'previous-date') { this.previousDate = newV; this.previousDateEl?.textContent = `last week - ${newV}Hs`; } } } customElements.define('time-panel', TimePanel)
内容的提问来源于stack exchange,提问作者nazarenoalt
相关产品推荐
相关产品推荐

