Web Components:原生组件化开发的新探索实践
开篇背景

我在一家中型互联网公司负责前端产品的开发工作,主要负责内部系统的 UI 组件库建设以及部分中后台页面的开发。随着团队规模扩大、项目复杂度上升,我们开始频繁面对一个老问题:“重复造轮子”——每个小组都在各自维护一套类似的按钮、输入框、弹窗等基础组件。这不仅浪费时间,还导致风格不统一,后期维护成本极高。
为了更好地管理组件复用与样式一致性,我们尝试过引入 React 和 Vue 的组件体系。但有些项目基于 jQuery 或纯 HTML/CSS/JS 实现,引入框架反而显得“重了”。这时候我突然想到以前听别人提过的 Web Components,于是决定带着团队做一个轻量级的组件封装方案进行试点。
这篇文章将结合我们实际落地过程中遇到的问题和解决方式,谈谈我对 Web Components 在现代前端开发中的新定位的理解与看法。
问题描述:传统方案难以满足需求

场景一:组件重复、逻辑割裂
有一个报表页面在多个业务线都有使用,但各自都复制了一份 HTML+CSS+JavaScript 到自己的项目中。后来当某个交互需要调整时,三个组同步改代码成了常态,效率极其低下,而且很容易出错。
场景二:技术栈差异带来的整合困难
公司有一部分系统是基于 jQuery 写的旧项目,还有一部分是 Vue 单页应用(SPA),还有几个新上线的项目采用了 React + TypeScript。想共享一个基础 UI 库变得异常复杂,因为要为每个技术栈分别封装不同版本的组件,维护难度极大。
场景三:样式污染严重
由于没有模块化机制,很多项目都是全局 CSS 样式,不同组件之间相互干扰。比如两个不同的“Tab 组件”,样式规则可能冲突,最终显示效果完全不可控。
这些问题促使我们重新审视架构设计思路,也让我对 Web Components 产生了兴趣,并希望通过它来解决这些痛点。
解决方案:采用 Web Components 做基础组件封装
技术选型思考
起初我也犹豫 Web Components 是否适合生产环境,担心兼容性和性能。但在查阅官方文档、社区生态后发现,主流浏览器(Chrome、Edge、Firefox)都已经很好地支持 Custom Elements v1 和 Shadow DOM API,Safari 从 2017 年开始也基本可以胜任使用场景,所以决定试水。
我们的目标很明确:
- 统一核心 UI 组件封装方式
- 支持跨技术栈复用(Vue / React / jQuery)
- 避免样式污染
- 提供简单的接口供调用者扩展功能
我们的实现方案简述
以最常用的 <custom-button> 为例,这是我们在项目中第一个尝试封装的 Web Component:
// custom-button.js
class CustomButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
const text = this.getAttribute('text') || '默认文本';
const variant = this.getAttribute('variant') || 'primary';
this.shadowRoot.innerHTML = `
<style>
button {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
color: #fff;
border: none;
background-color: ${variant === 'primary' ? '#007bff' : '#6c757d'};
cursor: pointer;
}
</style>
<button>${text}</button>
`;
}
}
window.customElements.define('custom-button', CustomButton);
然后在任意项目里都可以这样直接使用:
<!-- 页面上直接调用 -->
<custom-button text="提交" variant="primary"></custom-button>
通过这种方式,我们实现了组件定义与消费分离。
进阶处理事件与数据通信
Web Components 天然支持 DOM 元素的操作,因此我们可以非常方便地触发事件或者传递状态。例如给按钮加上点击事件通知父组件:
this.shadowRoot.querySelector('button').addEventListener('click', () => {
const event = new CustomEvent('btn-click', {
detail: { value: 'some info' },
bubbles: true,
});
this.dispatchEvent(event);
});
消费方监听这个事件:
<custom-button id="my-btn" text="点我试试" />
<script>
document.getElementById('my-btn').addEventListener('btn-click', (e) => {
console.log('按钮被点击了!携带的数据:', e.detail.value);
});
</script>
这样无论外部用的是 jQuery 还是 Vue,都可以轻松集成并监听到组件内部抛出的事件。
落地挑战与解决方案

挑战一:样式隔离的误解
一开始我们误以为 Shadow DOM 可以完全隔绝对外样式的侵染,但实际上如果组件本身嵌套在有某些特殊上下文的页面中,比如全局设置了 a 标签的样式,则仍可能导致影响组件内的链接。
为此,我们增加了一个工具方法,在组件内主动重置一些关键样式:
a {
all: unset;
text-decoration: none;
}
同时鼓励团队采用 CSS-in-JS 方案,进一步增强可维护性。
暂未支持 IE 的取舍
由于 Web Components 对 IE11 支持较弱,而公司目前还有少量客户仍在使用该浏览器。我们进行了评估,认为当前阶段不适合强推 Web Components 在这部分环境中使用。因此我们做了如下策略:
- 重要系统保留 jQuery 方案
- 新项目优先采用现代方案(如 Web Components)
- 提供 Polyfill(如 webcomponents-bundle.js)作为过渡方案
- 后续逐步推动客户迁移至现代浏览器
这是一个务实的做法。
性能优化:减少初次加载阻塞
我们曾遇到一个问题:组件注册太晚,页面渲染的时候出现未识别标签错误(如 Unknown Element)。我们采取了以下优化:
- 将 Web Components 打包成单独的 JS 文件
- 使用 defer 属性确保在 DOM 加载完毕后再执行组件注册
- 对非核心组件采用懒加载策略
这样既避免阻塞首次渲染,又保持结构清晰。
效果总结:带来的收益有哪些
经过两个月的试点项目,我们在以下方面收获颇丰:
✅ 显著减少重复代码
UI 组件统一收归为独立库后,业务侧只需引入一次即可反复调用。减少了多个版本的 Button、Modal 等常见组件的数量,降低维护压力。
✅ 支持跨技术栈调用
无论是 Vue 的单文件组件还是 jQuery 页面,都能以标准 HTML 元素的方式调用封装好的 Web Component,真正做到“一处定义,多处可用”。
✅ 更好控制样式隔离
得益于 Shadow DOM,各组件之间的样式互不影响,极大降低了全局 CSS 的污染风险。
✅ 推动团队工程规范统一
Web Components 鼓励大家以“自定义标签”的形式思考组件设计,让整个前端团队形成了更一致的开发习惯。
经验分享与建议
作为一个亲自踩坑的人,我想分享几点心得和建议:
1. 不要试图用 Web Components 替代 React/Vue
虽然 Web Components 很强大,但它并不是现代框架的替代品。它更适合用来做基础组件层的封装或插件化能力暴露,而不是直接构建复杂的交互逻辑。
如果你的应用本身是复杂的 SPA,那最好还是用 React、Vue 或 Svelte 来组织整体结构,而把 Web Components 作为子组件或插件使用。
2. 关注浏览器兼容性 & Polyfill
虽然大部分现代浏览器已经原生支持 Web Components 标准,但仍需考虑老旧环境的支持情况。推荐在使用前加入如下代码确保安全:
<script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.4.3/webcomponents-bundle.js"></script>
不过要注意其体积及对性能的影响。
3. 重视开发调试体验
Web Components 是真正的“黑盒”组件,它的 Shadow DOM 结构不能轻易查看。为了便于调试,建议:
- 添加 debug 工具类输出 shadow root 内容
- 使用浏览器 DevTools 的 Elements 面板观察内部结构变化
- 在开发阶段添加日志辅助排查
4. 保持语义清晰 & 可访问性
Web Components 最大的优势之一是它可以成为语义化的 HTML 标签。所以在命名组件时尽量使用具有含义的名称,比如:
✅ 推荐:<app-header>, <ui-card>, <data-grid>
❌ 不推荐:<comp-xxx>, <x-component>
同时注意 A11Y(无障碍访问),特别是表单元素和按钮,确保它们具备 tabindex、role 和 aria-* 属性。
5. 不要忽视打包和工程化
即便 Web Components 是原生能力,你也仍然需要良好的工程流程来进行打包、测试、部署。我们采用 Rollup 构建组件库,配合 ES Modules 形成标准导入导出机制,极大提升了开发效率。
写在最后:未来展望与趋势判断
Web Components 作为一种“原生组件化方案”,近年来正逐渐被各大主流框架所接纳。React 官方已提供自定义元素的支持文档;Vue 也提供了 useElementPlus 注册自定义元素的方法。
我认为,Web Components 正成为一种“桥梁性”技术 —— 它不仅可以帮助我们构建更加通用的组件库,还能作为微前端架构下的自然通信方式。尤其在未来 Web 生态日益碎片化的背景下,标准化的组件交互方式尤为重要。
作为一名一线开发者,我很高兴看到 Web Components 在真实项目中发挥出它的价值,也期待它能在更多项目中得到广泛采纳。希望这篇分享能给你带来启发,少走弯路。
如果你也开始尝试 Web Components,欢迎留言交流经验 👇
如有疑问或希望获取本文示例代码,也可以在 GitHub 私信我或在评论区留下你的联系方式。咱们一起进步 😄

评论 0