Web Components 现代实践:标准化、可靠跨框架组件的完整指南

HTMLPAGE 团队
14 分钟阅读

从 Custom Elements、Shadow DOM、HTML imports 到开发者体验,讲清楚 Web Components 今天真实可用的状况、适用场景与实战踩坑指南。

#Web Components #Custom Elements #Shadow DOM #Standards #Framework Agnostic

Web Components 现代实践:标准化、可靠跨框架组件的完整指南

Web Components 被吹捧了 10+ 年,为什么到今天仍然"不那么流行"?

不是技术问题,而是心智问题:很多人以为 Web Components 是想替代 React/Vue,实际上它们是互补的。真实的场景是:

  • 公司有多个框架的项目,想共享 UI 组件库
  • 想写一个与框架无关的插件/扩展
  • 产品要集成第三方贡献的 widget

在这些场景下,Web Components 就特别有用。


1. Web Components 的三大核心

Custom Elements

定义你自己的 HTML 标签,像这样:

class MyButton extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' })
    this.shadowRoot.innerHTML = `<button>Click me</button>`
  }
}
customElements.define('my-button', MyButton)

然后就能 <my-button></my-button> 直接用。

Shadow DOM

组件内部的 DOM 树与外部隔离,样式不会互相污染。这对大型应用特别有用。

HTML Templates

<template> 标签存储待复用的 HTML 片段,不会被立即解析和渲染。


2. 实战建议:从 Custom Elements 开始

不要贪心同时学三个概念。先专注 Custom Elements:

class Counter extends HTMLElement {
  constructor() {
    super()
    this.count = 0
  }

  connectedCallback() {
    this.render()
  }

  render() {
    this.innerHTML = `
      <p>Count: ${this.count}</p>
      <button onclick="${this.increment.bind(this)}">+</button>
    `
  }

  increment() {
    this.count++
    this.render()
  }
}

customElements.define('my-counter', Counter)

这样写就能跨框架共享。


3. Shadow DOM 的两大价值

隔离样式

class StyledButton extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' })
    this.shadowRoot.innerHTML = `
      <style>
        button { background: blue; } /* 只影响组件内部 */
      </style>
      <button><slot></slot></button>
    `
  }
}

组件内的 button { background: blue } 不会影响页面其他按钮。

隔离 DOM 结构

选择器无法穿透 Shadow DOM 边界,保护组件内部实现细节。


4. Props、Attributes 与 Slots

Attributes(HTML 属性)

<my-button size="large" disabled></my-button>

在组件中读取:

const size = this.getAttribute('size')

传递简单值用 attributes。

Properties(JS 属性)

element.data = { userId: 123 }

传递复杂对象用 properties。

Slots(内容分发)

<my-button>Click here</my-button>

组件模板中:

<button><slot></slot></button>

5. 事件通信

Web Components 推荐通过自定义事件通信:

class Counter extends HTMLElement {
  increment() {
    this.count++
    this.dispatchEvent(
      new CustomEvent('count-changed', {
        detail: { count: this.count },
        bubbles: true
      })
    )
  }
}

// 使用
document.querySelector('my-counter').addEventListener(
  'count-changed',
  (e) => console.log('新 count:', e.detail.count)
)

好处是框架无关,任何地方都能监听。


6. 与现代框架的集成

虽然 Web Components 框架无关,但近年 React、Vue、Angular 都改进了支持。

Vue 中用 Web Components

<my-custom-element :prop="value" @custom-event="handler"></my-custom-element>

React 中用 Web Components

<MyCustomElement prop={value} onCustomEvent={handler} />

只要属性名和事件名约定好,就能无缝集成。


7. 实际痛点与解决方案

痛点 A:手写 querySelector 太繁琐

方案:用第三方库如 lit-element or fast-element,降低模板和状态管理成本。

痛点 B:样式怎么深度定制

方案:暴露 CSS Variables:

this.shadowRoot.innerHTML = `
  <style>
    button { 
      background: var(--button-bg, blue);
    }
  </style>
`

使用方可以 --button-bg: red 覆盖。

痛点 C:在线编辑器能用吗

方案:GrapesJS 等编辑器可以通过 iframe 或动态脚本加载 Web Components,但有限制。


8. 选择建议

适合用 Web Components:

  • 多框架共享 UI 库
  • 建设 Design System & 跨项目复用
  • 第三方 widget 集成
  • 基础设施类的独立功能

不适合:

  • 单个 React/Vue 项目的内部组件(用原生框架更简洁)
  • 需要很强的状态管理和开发者工具的复杂应用

相关阅读