Web Components:一次组件化的“返璞归真”之路
开篇:为什么我要写这篇文章?

作为前端工程师,在过去的五年中,我有幸参与过不少大大小小的项目。从最初的jQuery时代一路走来,经历过Angular、React和Vue的更迭与成长。但直到最近半年,我才真正感受到一种前所未有的平静——那是在我们决定使用 Web Components 构建一个跨框架共享 UI 组件库时。
这并非一时兴起的选择,而是源于多个项目的积累教训和反复试错后的理性判断。我想把这些经验整理出来,分享给大家,尤其是那些在组件化开发之路上遇到瓶颈的同学。希望这篇来自真实踩坑经历的技术文章,能带给你一些启发或避坑经验。
问题描述:组件共享难题

故事要从去年的一个项目说起。当时我们公司正在做一套企业级产品系统,分为几个子产品线,分别由不同的团队负责。这些产品虽然功能不同,但用户界面风格高度统一,甚至有大量交互一致的组件(比如按钮、弹窗、表格等)。
最开始,我们尝试用 Vue 的 NPM 包封装基础组件,每个项目安装依赖后直接使用。结果理想很丰满,现实却很骨感:
- 版本混乱:不同项目依赖的组件版本不一致,升级困难;
- 耦合性高:组件内部依赖 Vue 的 API,导致非 Vue 项目无法使用;
- 打包冲突:第三方库引入多个 Vue 实例,页面出现不可预知的错误;
- 协作障碍:不同技术栈之间难以复用组件,沟通成本极高。
这个问题困扰了我们一段时间,也促使我们重新思考一个问题:
我们真的需要通过框架绑定来构建组件吗?
解决方案:Web Components 来救场

经过一番调研,我们最终将目光投向了原生支持的 Web Components 技术。它的核心理念很简单也很强大:
用浏览器原生的能力,定义可复用、独立且跨框架的 UI 组件。
我们决定采用 Web Components 来重构整个组件库,实现真正的“一份代码,多端复用”。目标非常明确:
- 构建一个不依赖任何框架的组件库;
- 所有子产品线可以直接使用;
- 支持未来接入更多技术栈(如 React、Svelte)。
听起来是不是有点像“梦想回归现实”?不过别急,这条路上的坑远比你想象得多。
实践过程:Web Components 真实落地之旅

我们的第一步是选型。
技术选型与工具链搭建
我们没有选择从头造轮子,而是借助了一个轻量级的帮助库 LitElement(后来改为 Lit),它大大简化了 Web Components 的开发流程。
主要优势包括:
- 轻量级:几乎没有额外负担
- 模板引擎好用(
html模板标签) - 自动更新机制智能
- 与原生标准兼容性极佳
工具链方面我们使用:
- Vite + TypeScript 构建开发环境
- Rollup 打包生产模块
- Jest + Playwright 做单元测试和 E2E 测试
- Storybook 做组件演示文档
一切准备就绪后,我们开始了第一个组件—— <custom-button> 的开发。
第一个组件:<custom-button>
import { LitElement, html, css } from 'lit'
import { customElement, property } from 'lit/decorators.js'
@customElement('custom-button')
export class CustomButton extends LitElement {
@property({ type: String }) label = ''
@property({ type: Boolean }) disabled = false
static styles = css`
button {
padding: 8px 16px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`
render() {
return html`
<button ?disabled=${this.disabled}>
${this.label}
</button>
`
}
}
没错,这就是一个典型的基于 Lit 的 Web Component 定义方式。它完全封装了结构、样式和行为,而且无需任何框架支持。
踩坑记录:真实踩过的那些坑
说了半天美好的设想,接下来才是本文的重点部分:实际开发中踩到的真实大坑和解决方法。这部分我将详细分享几个关键问题和我的应对思路。
坑1:浏览器兼容性不如预期
你以为 Web Components 是现代浏览器的标准就能无脑上?Too young too simple。
在项目初期我们只测试了 Chrome 和 Edge,上线前发现某些老客户的 IE11 还没下线,根本无法渲染组件!
解决办法:
- 使用官方 Polyfill:
@webcomponents/webcomponentsjs - 针对旧浏览器自动加载:
<script src="path/to/webcomponents-bundle.js" defer></script>
- 编译配置加入 Babel + ES5 转换:
// vite.config.ts 配置示例
optimizeDeps: {
esbuildOptions: {
target: 'es2015'
}
}
坑2:样式隔离的边界模糊
Web Components 默认的 Shadow DOM 隔离了内部样式,看起来美好得像是解决了所有问题。但在实战中发现,有时候需要全局样式控制组件主题色、字体等通用设置。
我的做法是:
- 使用 CSS 变量作为接口暴露给外部:
static styles = css`
:host {
--btn-bg: #4caf50;
--btn-color: white;
}
button {
background-color: var(--btn-bg);
color: var(--btn-color);
}
`
这样,上层应用可以通过修改变量来定制组件样式,同时又不会破坏内部样式结构。
坑3:事件传递不透明,调试难
Web Components 的事件默认都是原生 DOM 事件,但很多时候我们需要自定义事件(比如点击按钮时传参数)。
举个例子,我们希望在按钮点击时触发一个 custom-click 事件并传递数据:
const event = new CustomEvent('custom-click', {
detail: { source: 'button', value: this.label },
bubbles: true,
composed: true
})
this.dispatchEvent(event)
但奇怪的是,我们在父级框架里监听这个事件的时候有时候收不到。
最终排查原因:
- 是否设置了
bubbles: true? - 是否设置了
composed: true?这是跨 Shadow DOM 传播的关键。 - 框架是否拦截/重写了原生事件?(Vue 没问题,有些框架需要手动处理)
坑4:性能优化不能掉以轻心
Web Components 本身不会自动帮你优化性能。尤其是频繁创建和销毁大量组件实例时,容易引发内存泄漏或卡顿。
我们之前在列表页使用 <custom-list-item> 渲染数千行数据,一度出现卡顿现象。
后来的优化方案:
- 列表组件采用虚拟滚动(Virtual Scrolling),减少节点数量
- 通过 WeakMap 缓存复杂计算结果
- 对高频函数加防抖节流(特别是 resize 或 scroll 相关逻辑)
- 小组件尽量静态化,避免过多响应式更新
效果总结:收益显著
这套 Web Components 组件库上线后,我们收获了很多意料之外的好处:
✅ 组件彻底解耦,可在任意框架中自由使用
现在我们有一个 React 项目也在用这个组件库,只需要简单封装一个适配器即可:
function App() {
return (
<custom-button label="提交" onClick={(e) => console.log(e.detail)} />
)
}
✅ 多项目版本一致性大大提高
所有项目都引用同一个包,更新只需发布 npm 版本,极大减少了人工同步成本。
✅ 开发效率提升,UI 标准更加统一
有了 Storybook 文档 + 设计稿还原度高的组件库,新成员几乎不需要花时间熟悉 UI 交互细节。
心得体会:组件化开发的本质
在这次 Web Components 探索之旅中,我最大的感悟是:
组件化的核心不是技术,而是设计思维。
很多开发者一上来就想找现成的组件库,“能用就行”,但忽略了最重要的事情:你的组件库是否具备扩展性、可维护性和业务理解能力?
Web Components 强大的地方就在于它把组件交还给了开发者——你可以按自己的规则组织 UI 结构,而不是被框架牵着鼻子走。
给读者的建议:避开 Web Components 的常见误区
最后,结合我个人的经验,给正准备尝试 Web Components 的同学们几点建议:
🧱 不要盲目追求“纯原生”
Web Components 是原生的,但不代表你必须从零开始开发。合理使用工具库(如 Lit、Stencil)可以大幅提升开发体验和效率。
🧭 提前做好组件规划
不要边开发边改接口。每个组件的属性、事件、生命周期应该一开始就有清晰的设计文档,避免后期大规模重构。
🛠️ 工具链不要轻视
TypeScript、Storybook、Rollup、测试框架一个都不能少。否则你会在后续维护中吃尽苦头。
🔄 主动拥抱变化
Web Components 目前发展迅速,新的提案(如 Declarative Shadow DOM)和优化不断涌现。保持关注社区动态,及时迭代你的方案。
总结:Web Components 是未来的基石
回望这几年的技术演变,从 jQuery 到 SPA 再到组件驱动开发,Web Components 正在成为连接过去与未来的桥梁。
它未必适合每一个项目,但它确实提供了一种标准化、去中心化的组件解决方案,尤其适用于大型多团队协作、长期维护的项目。
如果你所在的团队也有类似的困境,不妨试试 Web Components。也许这条路并不平坦,但一旦走过,你会发现:
有些技术回归本质,反而走得更稳,也看得更远。
感谢你读到这里。如果文中有什么启发或者疑问,欢迎留言交流。也欢迎你将这篇文章分享给同样在组件化道路上挣扎的同学,愿你在 Web Components 的旅途中少走些弯路。

评论 0