Web Components 与 Shadow DOM 实战

React 写的组件不能在 Vue 里用,Vue 组件也不能在原生 HTML 里跑。Web Components 是浏览器原生的组件标准,写一次可以在任何框架甚至不用框架的页面里直接用。本文从自定义元素到 Shadow DOM,讲清这套原生组件方案。

Web Components 配图:蓝色渐变背景上嵌套盒子容器与 Shadow DOM 边界隔离,扁平科技插画
Web Components:浏览器原生的组件封装与跨框架复用标准

一、什么是 Web Components

Web Components 不是一个单一技术,而是浏览器原生提供的一组 API:自定义元素(Custom Elements)、Shadow DOM、HTML 模板(Template)。它们合起来让你可以创建自己的 HTML 标签,比如 <user-card name="小明"></user-card>,而且这个标签的样式和 DOM 是封装隔离的,不会被外部页面影响。

技术作用
Custom Elements定义自定义 HTML 标签及其行为
Shadow DOM封装组件内部 DOM,外部 CSS 进不去
HTML Template声明组件的 HTML 结构模板

二、第一个自定义元素

class GreetingCard extends HTMLElement {
  constructor() {
    super()
    // 创建 Shadow DOM 隔离
    const shadow = this.attachShadow({ mode: 'open' })

    const name = this.getAttribute('name') || '朋友'
    const style = document.createElement('style')
    style.textContent = `
      .card {
        background: #f0f7ff;
        border: 1px solid #1e5fa8;
        border-radius: 8px;
        padding: 16px;
        font-family: sans-serif;
      }
    `
    const div = document.createElement('div')
    div.className = 'card'
    div.textContent = `你好,${name}!欢迎使用 Web Components`

    shadow.appendChild(style)
    shadow.appendChild(div)
  }
}

// 注册自定义元素:标签名必须带短横线
customElements.define('greeting-card', GreetingCard)
<!-- 在页面中直接使用 -->
<greeting-card name="小红"></greeting-card>
<greeting-card name="小刚"></greeting-card>
关键点:自定义元素的标签名必须包含短横线(如 greeting-card),避免和原生 HTML 标签冲突。attachShadow({mode:'open'}) 开启 Shadow DOM,组件内部样式和外部页面完全隔离。

三、Shadow DOM 的封装力

没有 Shadow DOM 时,你写的组件样式会泄漏到页面其他地方,页面样式也会影响组件。Shadow DOM 把组件内部变成一个独立的 DOM 树,外部 CSS 选择器穿不透。

// 组件内部的样式
style.textContent = `
  p { color: red; }  // 只影响组件内部的 p 标签
`

// 外部页面的样式
// p { color: blue; }  // 不会影响 Shadow DOM 内部的 p 标签

这就是 Web Components 能跨框架复用的核心原因:样式和 DOM 都是封装好的,扔到任何页面里都不会打架。

四、属性与响应式

class UserCard extends HTMLElement {
  static get observedAttributes() {
    return ['name', 'avatar']  // 监听哪些属性变化
  }

  attributeChangedCallback(name, oldValue, newValue) {
    this.render()
  }

  connectedCallback() {
    this.render()
  }

  render() {
    const shadow = this.attachShadow({ mode: 'open' })
    const name = this.getAttribute('name') || '匿名'
    shadow.innerHTML = `
      <div class="user">
        <img src="${this.getAttribute('avatar') || '/default.png'}">
        <span>${name}</span>
      </div>
    `
  }
}
customElements.define('user-card', UserCard)

observedAttributes 声明要监听的属性,属性变化时自动调用 attributeChangedCallback。

五、在框架中使用

// React 中使用 Web Components
function App() {
  return (
    <div>
      <user-card name="小明" avatar="/me.png"></user-card>
    </div>
  )
}

// Vue 中使用 Web Components
<template>
  <user-card name="小红" avatar="/her.png"></user-card>
</template>

// 原生 HTML 中直接用
<user-card name="小刚"></user-card>

Web Components 就是普通 HTML 标签,任何框架都能识别。这就是它相比框架专属组件的最大优势:一次开发,到处运行。

六、什么时候用 Web Components

适合用的场景:设计系统组件库(按钮、卡片、弹窗)、需要跨多个项目或团队复用的组件、微前端架构中的独立子应用。

不适合的场景:重度交互的复杂应用(状态管理不如 React/Vue 方便)、需要极致渲染性能的列表(虚拟滚动还是框架更成熟)。

七、总结

Web Components 是浏览器原生的组件方案,Shadow DOM 提供了样式和 DOM 的封装隔离,自定义元素让你定义自己的 HTML 标签。它的最大价值是跨框架复用:写一个 <user-card>,React 项目能用,Vue 项目能用,甚至纯 HTML 页面也能直接用。对于设计系统和跨团队共享组件,Web Components 是值得投入的方向。