2026-10-18 13:50:31
通过 JavaScript 的 Web Components 实现真正的组件复用,需围绕其核心技术(Shadow DOM、自定义元素、HTML 模板)构建,并遵循隔离性、语义化、灵活性和解耦原则。 以下是具体实现方法:
1. 利用 Shadow DOM 实现样式与结构隔离在自定义元素类中通过 attachShadow({ mode: 'open' }) 挂载影子根。
将组件的 HTML 结构和样式写入 shadowRoot,例如:class MyButton extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = ` <style> button { background: blue; color: white; } </style> <button>Click Me</button> `; }}customElements.define('my-button', MyButton);
效果:my-button 的样式不会影响页面其他部分,外部 CSS 也无法覆盖其内部样式。
使用 customElements.define(name, class) 注册组件,命名需包含短横线(如 my-card)。
通过 observedAttributes 声明可监听的属性,并在 attributeChangedCallback 中响应属性变化:class MyButton extends HTMLElement { static get observedAttributes() { return ['disabled']; } attributeChangedCallback(name, oldValue, newValue) { if (name === 'disabled') { this.shadowRoot.querySelector('button').disabled = newValue !== null; } }}customElements.define('my-button', MyButton);
使用示例:<my-button disabled></my-button> 可直接通过属性控制组件状态。
在组件模板中使用 <slot> 定义占位符,例如:class MyCard extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = ` <div class="header"> <slot name="header">默认标题</slot> </div> <div class="content"> <slot>默认内容</slot> </div> `; }}customElements.define('my-card', MyCard);
使用示例:通过 slot 属性分发内容:<my-card> <span slot="header">用户信息</span> <p>姓名:张三</p></my-card>
事件通信:通过 CustomEvent 触发自定义事件,例如:this.shadowRoot.querySelector('button').addEventListener('click', () => { this.dispatchEvent(new CustomEvent('btn-click', { detail: 'clicked' }));});外部可通过 addEventListener 监听事件:document.querySelector('my-button').addEventListener('btn-click', (e) => { console.log(e.detail); // 输出 'clicked'});
方法暴露:在组件类中定义公共方法,例如:class MyInput extends HTMLElement { reset() { this.shadowRoot.querySelector('input').value = ''; }}customElements.define('my-input', MyInput);外部可直接调用方法:const input = document.querySelector('my-input');input.reset();
在 HTML 中定义模板:<template id="my-card-template"> <div class="header"><slot name="header"></slot></div> <div class="content"><slot></slot></div></template>
在组件类中克隆模板内容:class MyCard extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); const template = document.getElementById('my-card-template'); this.shadowRoot.appendChild(template.content.cloneNode(true)); }}customElements.define('my-card', MyCard);
通过合理运用 Shadow DOM、自定义元素、插槽和事件通信,Web Components 可实现高内聚、低耦合的组件,具备以下优势:
示例完整代码:
// 定义组件模板(可提取到HTML中)const template = document.createElement('template');template.innerHTML = ` <style> button { padding: 8px 16px; background: #007bff; color: white; border: none; } </style> <button><slot>默认按钮</slot></button>`;// 自定义元素类class MyButton extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.shadowRoot.appendChild(template.content.cloneNode(true)); } static get observedAttributes() { return ['disabled']; } attributeChangedCallback(name, oldValue, newValue) { if (name === 'disabled') { this.shadowRoot.querySelector('button').disabled = newValue !== null; } } dispatchEvent(eventName, detail) { this.shadowRoot.querySelector('button').dispatchEvent( new CustomEvent(eventName, { detail, bubbles: true }) ); }}// 注册组件customElements.define('my-button', MyButton);此实现满足样式隔离、语义化、灵活性和解耦需求,是真正的跨框架复用方案。