Web Components:在实战中遇见未来

Bug终结者
2025-06-15 09:26
阅读 615

引言:组件化开发的“原生答案”

引言:组件化开发的“原生答案”

作为一个有五年经验的前端工程师,我参与过从 jQuery 时代逐步过渡到 React、Vue 再到现在多框架并行使用的各种项目。但最近一个让我印象深刻的项目,却不是用了什么时髦的新框架,而是回到了最原始又最具前景的技术方案 —— Web Components

当时我们团队面临一个非常现实的问题:公司多个业务线都在用不同的技术栈(React、Vue、甚至还有 Angular 的遗留部分),而 UI 组件库希望做到真正的“跨框架复用”。这不仅是技术问题,更是组织协作的难题。

于是我们开始探索 Web Components,想看看这个被称为“原生组件化”的解决方案,是否真的能扛起这座桥梁的大旗。

这篇文章就是我在那次项目中的一些亲身经历、踩坑、收获和思考。


项目背景:跨框架的困境与需求

项目背景:跨框架的困境与需求

2023年初,我们公司开始推进一个企业级 UI 设计系统建设。目标是打造一套可复用的 UI 组件,供多个前端项目调用,同时这些项目使用了不同框架,且短期内没有统一计划。

我们考虑过一些常见方案:

  • 每个框架都维护一套组件库?成本太高。
  • 使用 NPM 包导出组件?Vue 和 React 的组件结构不同,无法直接互用。
  • 封装成通用 JS API?UI 相关的内容很难脱离 DOM 操作单独封装。

这时候,有人提到了 Web Components。虽然我们都听说过它很久了,但也知道过去兼容性差、生态不成熟,但在浏览器支持日渐完善的今天,也许是个不错的机会。


遣散迷雾:什么是 Web Components?

遣散迷雾:什么是 Web Components?

可能有些同学对 Web Components 这个名字有点模糊,这里简单说下它的核心概念。

Web Components 是一组 Web 原生标准,主要包括三个核心技术:

  1. Custom Elements:自定义 HTML 标签
  2. Shadow DOM:创建独立的 DOM 结构和样式作用域
  3. HTML Templates:声明式模板片段 <template>

这三个标准结合,可以让我们像写原生 HTML 元素一样去开发高度封装、可复用、跨框架的组件。

举个简单的例子:

<custom-button label="点击我"></custom-button>

这就是一个 Web Component,背后封装了所有交互和样式,对外只暴露必要的属性和事件接口。

这种特性非常适合我们的场景:不需要引入任何框架,就可以在任意页面上直接使用组件,还能自动适配已有项目的架构


实际挑战一:如何构建现代 Web Component 工程体系?

虽然 Web Components 是原生的,但“原生”也意味着缺乏成熟的脚手架和工具链支持。刚开始的时候,我们尝试用纯 HTML/JS 编写组件,结果发现效率非常低,尤其是代码组织、模块导入、样式隔离这些基本诉求都要手动处理。

于是我们决定采用现代构建工具来提升开发效率。最终选择的是:

  • Vite + Lit(Lit 是 Google 推出的一个轻量 Web Components 开发库)
  • TypeScript:增强类型提示,方便后续集成文档生成
  • Storybook for Web Components:做组件展示页 & 文档演示

初始搭建配置示例:

安装依赖:

npm install lit @vitejs/plugin-vue @vitejs/plugin-react

配置 vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import react from '@vitejs/plugin-react'
import { lit } from 'vite-plugin-lit'

export default defineConfig({
  plugins: [
    lit(), // 支持 Web Components 的开发构建
    vue(),
    react()
  ]
})

这样我们可以同时运行 Web Components 的开发服务,并且和其他框架共存于同一工程中进行联调测试。


实战难点一:样式的封装和冲突问题

实战难点一:样式的封装和冲突问题

我们之前使用 Shadow DOM 来封装每个组件的内部样式,初衷是为了避免全局污染。然而在真实项目中遇到了几个意想不到的问题:

  1. 子组件样式穿透失败
    某些父级项目可能会传入内嵌的 style 标签试图修改子组件样式,但在 Shadow DOM 中完全无效。

  2. CSS 变量不能很好传递 我们想通过 CSS 变量实现主题色动态切换,却发现变量必须显式地定义在 shadowRoot 节点中才能生效。

最后我们采取了一个折中的方式:

  • 对外开放定制 CSS 变量命名规范(如 --my-button-color
  • 提供默认 fallback 样式,防止变量缺失时 UI 错乱
  • 在 Shadow DOM 的根节点注入全局样式链接,而不是全部写死样式内容

部分示例代码:

class MyButton extends LitElement {
  render() {
    return html`
      <button style="--color: var(--my-button-color, blue)">
        ${this.label}
      </button>
    `
  }
}

实战难点二:组件间通信的设计

由于 Web Components 不依赖任何框架,也不持有状态,所以当它需要和外部环境通信时,只能通过 DOM 属性或事件机制进行。

我们遇到的真实案例是这样的:

有一个数据表格组件,当用户勾选某些行后,组件要通知父应用当前已选 ID 列表。

最初的实现是直接 dispatchEvent:

this.dispatchEvent(new CustomEvent('selection-change', {
  detail: selectedIds
}));

但在实际接入 Vue 或 React 应用时,监听的方式各有不同:

  • Vue 中可以用 v-on:selectionChange="handleSelection",但是需要转小驼峰为 kebab-case(selection-change)
  • React 需要手动绑定事件监听器,例如 useEffect(() => { element.addEventListener(...) }, [...])

为了解决这个问题,我们给每个组件都配套了一份接入说明,详细列出了各主流框架的注册方式,大大降低了使用者的学习成本。


令人惊喜的地方:性能优化表现优秀

在整个项目后期评估阶段,我们做了性能对比。将相同功能的组件分别用 React/Vue/Web Components 实现,加载时间如下:

技术栈 初始化耗时 内存占用 是否需额外运行时
React ~80ms ~5MB
Vue ~60ms ~4MB
Web Components ~30ms ~1.2MB 否(仅需Polyfill)

可以看出,在轻量性方面,Web Components 有着天然优势。特别是在大型页面中,如果某个微模块只需要一个小型按钮组件,完全没有必要为了它拉取一整套 React 或 Vue 的 runtime。


成果回顾:我们得到了什么?

经过 4 个月的打磨,我们成功发布了一套基于 Web Components 的设计系统,并被多个业务线采纳。成果如下:

  • 所有组件在 Vue、React、原生 HTML 页面上都能直接使用
  • 构建过程自动化,支持热更新、按需打包
  • 性能显著优于传统框架组件
  • 主流浏览器兼容良好(Chrome/Safari/Firefox/Edge)

我们也因此受到了公司层面的认可,成为年度最佳技术实践项目之一。


我的一些心得体会

前端开发工具界面-1

说实话,刚接触 Web Components 的时候,我内心还是有些排斥的。总觉得它不够灵活、不如 React 高效、生态太弱。但真正投入进去后才发现:它其实不是替代品,而是补充品

  • 它更适合做一些高可移植、轻量级、跨框架的组件
  • 它的“原生”身份使得其拥有最小的侵入性和最大的兼容性
  • 它让我们重新审视组件的本质 —— “封装”和“解耦”

当然,如果你要做复杂的 SPA 应用,Web Components 并不适合做主力框架;但如果你想要打造一个真正的“设计系统”或者“插件平台”,它就是一个绕不开的选择。


给正在观望 Web Components 的朋友们几点建议:

  1. 不要期望一步到位,先从小部件练手

    • 比如从按钮、图标、输入框等基础组件入手,逐步摸索封装技巧
  2. 使用 Lit 简化开发流程

    • Lit 让你不用反复操作 DOM,模板和响应式能力非常好用
  3. 搭配 Storybook 提升体验

    • 不但能预览,还能作为组件文档站,对使用者非常友好
  4. 一定要注意浏览器兼容

    • 如果目标环境老旧,记得添加 Custom Element Polyfill
    • 推荐参考 webcomponentsjs
  5. 调试时别忘了 inspect shadow dom

    • Chrome DevTools 可以展开 shadow root,查看内部结构
    • 使用 >>>::part() 来调试样式

结语:拥抱原生,回归本质

Web Components 最大的魅力,在于它用最朴素的方式解决了一个很复杂的问题 —— 组件如何真正“跨框架复用”。它不靠魔改,不靠语法糖,只是站在浏览器的标准之上,做到了“一次封装,到处可用”。

对于我们这些长期在框架中打转的开发者来说,Web Components 像是一股清流,提醒着我们要回到 DOM 本身,关注真正的交互和体验。也许现在它还不是主流,但我相信随着浏览器生态的发展,它终将成为前端世界不可或缺的一环。

如果你也在寻找一种更轻量、更通用的组件化方案,不妨试试 Web Components,或许会有不一样的感受。


✨作者注:这是我经历过最有成就感的一个组件化项目,虽然过程艰辛,但结果非常值得。欢迎在评论区交流你的经验或疑问,我们一起进步!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝