Web Components:一次彻底解放组件复用的尝试
开篇:为什么我要写这篇文章?
作为前端开发工程师,我们每天都在和“组件”打交道。React、Vue、Angular 这些框架早已深入人心,甚至成为了行业标配。然而在实际工作中,我逐渐发现了一个问题:当我们试图跨项目、跨团队、甚至跨技术栈共享 UI 组件时,这些框架的优势反而变成了掣肘。
有没有一种更轻量、更原生、更具通用性的组件化方式?直到有一次,我在一个大型微前端项目中接触到 Web Components,才发现原来 HTML 原生就支持这种能力。它不需要你绑定任何一个框架,也不需要引入庞大的构建流程。
这篇文章想跟你聊聊我亲身实践 Web Components 的经历——从最初的质疑到后来的坚定使用,以及过程中踩过的坑。如果你也在思考组件复用、跨团队协作、微前端集成等问题,或许这篇文章能给你带来一些启发。
问题描述:我们的组件,真的复用了吗?
事情发生在去年的一个企业级项目上,当时我们部门负责搭建一个中台系统,这个中台系统要为多个子业务线提供统一的 UI 界面、表单控件和数据展示组件。
原本我们是基于 Vue 开发的,也封装了若干组件,但随着不同业务线接入,麻烦来了:
- UI 组件无法跨团队使用:其他组有的用 React,有的还用 jQuery,组件根本没法直接搬过去。
- 样式冲突频繁:虽然用了 scoped 样式,但由于组件是通过 npm 包发布的,引入后样式依然相互影响。
- 版本管理困难:每次更新一个 bug,都要通知所有引用方更新依赖。
- 开发调试成本高:不同项目的环境配置差异大,本地测试很麻烦。
这时候我们就在想,有没有一种“原生”的方式,可以脱离任何框架进行组件定义和使用?
解决方案:Web Components,原生组件化的新思路
经过调研,我们决定尝试使用 Web Components 来重新封装核心组件。
什么是 Web Components?
简单来说,Web Components 是一组浏览器原生标准,主要包括以下几个关键技术点:
- Custom Elements(自定义元素)
- Shadow DOM(影子 DOM)
- HTML Templates(HTML 模板)
- ES Modules(ECMAScript 模块)
它的优势在于:
- 不依赖任何框架,纯 HTML/CSS/JS 实现
- 组件样式隔离(Shadow DOM)
- 可直接嵌入任意网页或应用
- 可发布为 NPM 包供多端使用
- 未来兼容性更好(因为它是原生标准)
我们的目标
- 将中台的核心 UI 组件(比如按钮、表格、弹窗等)重构为 Web Components
- 支持多种框架项目使用(Vue、React、jQuery 都行)
- 所有组件风格统一,便于维护
- 发布为独立 NPM 包,降低依赖耦合度
代码实践:一步步搭建你的第一个 Web Component
下面是一个简化版的按钮组件示例,演示如何用 Web Components 实现一个可复用的 <custom-button>。
HTML 模板
<!-- template.html -->
<template id="button-template">
<style>
.btn {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
.btn:hover {
background-color: #0056b3;
}
</style>
<button class="btn"><slot>Click Me</slot></button>
</template>
JavaScript 定义组件
// custom-button.js
class CustomButton extends HTMLElement {
constructor() {
super();
// 创建 Shadow DOM
const shadow = this.attachShadow({ mode: 'open' });
// 加载模板
const template = document.getElementById('button-template');
const clone = template.content.cloneNode(true);
shadow.appendChild(clone);
}
connectedCallback() {
const button = this.shadowRoot.querySelector('button');
button.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('custom-click', {
detail: { message: '按钮被点击啦!' },
bubbles: true,
composed: true
}));
});
}
}
// 注册组件
customElements.define('custom-button', CustomButton);
页面使用方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Button Demo</title>
<script type="module" src="./custom-button.js"></script>
</head>
<body>
<custom-button oncustom-click="handleClick()">Submit</custom-button>
<script>
function handleClick(event) {
console.log('Custom event:', event.detail.message);
}
</script>
</body>
</html>

是不是很简单?但这只是开始。
踩坑经验:那些年我们一起趟过的雷
刚开始的时候我们也是信心满满,结果实际落地过程中还是遇到不少问题,分享几个印象深刻的:
✅ 1. Shadow DOM 与全局样式冲突处理
一开始我们以为用了 Shadow DOM 就万无一失了,结果上线后发现某些字体没有继承过来。原来是有些公共 CSS 字体设置是在全局 <html> 上定义的,而 Shadow DOM 里并没有自动继承。
解决方案:
- 在组件内部单独引入字体文件(
@font-face),或者将字体变量抽离为 CSS 变量注入 - 使用
::part()和::theme()来允许外部定制特定区域的样式(现代浏览器已支持)
✅ 2. 表单组件无法提交的问题
我们在封装 <custom-input> 的时候发现,在父页面用 <form> 提交时,并不会触发事件。这是因为组件本身不是 native input 元素。
解决方法:
- 在组件内部监听用户输入,手动同步 value 到
<input>元素并暴露接口供外部获取 - 或者让组件包裹原生 input,同时封装其行为
✅ 3. 浏览器兼容性挑战
虽然大多数现代浏览器都支持 Web Components,但在一些旧客户现场仍然存在兼容性问题(IE11、老版本 Chrome)。
应对策略:
- 引入 webcomponents.js Polyfill
- 构建时区分目标浏览器环境,输出对应的 polyfilled 包
- 不再支持 IE11 后,移除 polyfill 减少包体积
✅ 4. 调试困难
Web Components 的 Shadow DOM 默认隐藏在 DevTools 中,调试起来不方便。
小技巧:
- 在 Chrome DevTools 设置里开启 "Show user agent shadow DOM"
- 使用
this.shadowRoot打印调试节点 - 组件内部加个 debug 标志,控制是否显示 Shadow DOM 结构
效果总结:这套组件体系带来了什么?
经过几个月的迭代,我们终于完成了第一期重构。效果还是挺明显的:
| 评估维度 | 之前 | 之后 |
|---|---|---|
| 组件复用难度 | 需适配不同框架 | 所有项目一键引用 |
| 样式冲突 | 高频发生 | 几乎零干扰 |
| 性能开销 | 较高(框架加载) | 轻量级组件 + 按需加载 |
| 调试便利性 | 靠 console.log | 可视化 Shadow DOM,配合 DevTools 得心应手 |
| 构建部署效率 | 依赖复杂构建流程 | 简单打包即可发布 |

另外,我们还将组件库打包上传到了私有 NPM 私服,各子项目只需:
npm install @company/shared-components
然后在入口 JS 中导入所需组件,就可以在任意 HTML 页面直接使用。
经验分享:给正在考虑 Web Components 的你
结合我这几年使用 Web Components 的经验,这里给你几点实用建议:
🧭 1. Web Components 是工具,不是银弹
它适合用于封装 高度稳定、交互逻辑相对固定、样式隔离要求高 的组件,例如:
- UI 控件(按钮、输入框、下拉菜单)
- 数据可视化图表
- 公共布局模块(Header、Footer)
- 外部嵌入脚本(比如广告位、统计埋点)
但对于交互复杂、状态管理频繁的组件,还是推荐使用成熟的框架来实现。
💡 2. 推荐搭配 ES Module + Rollup/Webpack 构建
虽然 Web Components 可以原生运行,但如果要跨项目使用并打包成 NPM 包,还是建议使用主流构建工具:
- 使用 Rollup 更加轻量,适合发布纯组件库
- 使用 Webpack 可整合 TypeScript、Babel、CSS modules 等高级功能
- 构建出 UMD、ESM、CommonJS 等多种格式,增强兼容性
🔍 3. 关注性能与可访问性
- Shadow DOM 虽好,但也可能导致渲染性能下降,尤其是嵌套过深
- 注意语义化标签和 Aria 属性的使用,提升无障碍体验
- 小组件可以考虑懒加载,用
IntersectionObserver监听首次可视再加载
🔒 4. 安全性也要关注
- Shadow DOM 隔离了样式,但不能完全防止 XSS
- 动态插入 HTML 内容要小心 Sanitize(消毒),避免执行恶意脚本
- 如果是开放平台组件,建议对属性传参做白名单校验
最后的感想
说实话,在用 Web Components 之前我也怀疑:“这玩意儿都出来这么多年了,为啥大家还在用 React/Vue?”
直到真正投入实战后,才意识到它填补了前端生态中的一个空白:一种真正跨框架、低耦合、可移植的组件形态。
它并不意味着取代现有的框架,而是一种补充和融合的机会。尤其是在微前端架构日益普及的今天,如果想要打造一套真正“通用”的 UI 组件库,Web Components 无疑是一个值得尝试的方向。
希望这篇来自我实际工作场景的文章能帮你少走弯路,也欢迎你在评论区留言交流经验。毕竟技术这条路,就是在不断尝试中前行的!
📌 文章源码仓库地址:github.com/dreamapplelee/web-components-demo
如有帮助,请给个小⭐️~

评论 0