Web Components:原生组件化开发的另一种可能
开篇:一次重构让我重新认识了Web Components

最近我参与了一个大型前端项目的重构,项目最初是用Vue 2搭建的,随着业务复杂度的上升,团队成员也逐渐增多。在迭代过程中我们遇到了很多重复性的问题——组件复用困难、样式污染严重、不同技术栈之间的协作成本越来越高。为了解决这些问题,我们在调研多种方案后决定尝试一个之前一直“听过但没怎么用”的技术:Web Components。
起初我也是持怀疑态度的,毕竟现在主流都是React、Vue这些成熟框架,Web Components看起来像是“古早”的产物。但在实际实践中发现,它其实在现代前端工程中依然有不错的适用场景,甚至在某些情况下比框架更有优势。
这篇文章我会结合我的真实项目经验,和你聊聊我是如何通过引入Web Components来解决实际问题的,以及过程中踩过的坑、收获的经验。
背景与挑战:为什么我们需要组件化?

我们的系统是一个内部使用的数据看板平台,用户主要是产品经理和运营同事。页面结构相对固定,但交互逻辑较复杂。由于模块多,每个功能板块之间耦合较高,常常一个小改动就需要全局测试。
问题集中在以下几点:
- 组件复用难:同一个图表组件在多个地方使用,但由于封装方式不同,经常需要手动复制修改。
- 样式冲突严重:Vue单文件组件虽然用scoped避免部分样式污染,但有时候因为第三方库混用导致样式穿透问题仍然存在。
- 跨团队协作成本高:我们有两个小组并行开发不同的子模块,技术栈不统一(一组用Vue,一组想用React),导致整合时出现不少适配问题。
- 性能瓶颈初现:页面加载慢、首次渲染时间长,尤其是在低网速环境下。
当时我们就思考:有没有一种更通用、更轻量的组件化方式?既能保持灵活性,又能减少依赖框架的程度?
答案就是我们开始尝试的方向——Web Components。
技术选型与思路:为何选择Web Components?
Web Components是一组浏览器原生支持的技术标准,包括:
- Custom Elements:允许自定义HTML标签
- Shadow DOM:提供隔离的DOM树和样式作用域
- HTML Templates:通过
<template>和<slot>实现内容分发
它的最大优点是无需任何框架依赖,可以直接在HTML中使用。这正好解决了我们两个小组用不同框架的问题。
我们最终决定将一些基础组件抽出来用Web Components重新实现。比如公共头部、表单项控件、弹窗、图表容器等。这些组件对性能敏感度不高,但强调稳定性和可复用性。
实践过程:从零开始构建一个Web Component
举个例子,我们要实现一个可搜索的选择器组件<searchable-select>。
1. 定义Custom Element
class SearchableSelect extends HTMLElement {
constructor() {
super();
this._shadowRoot = this.attachShadow({ mode: 'open' });
this.options = [];
}
connectedCallback() {
// 初始化组件UI
this.render();
this.loadOptions();
}
static get observedAttributes() {
return ['placeholder'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'placeholder') {
this._placeholder = newValue;
this.render();
}
}
async loadOptions() {
const resp = await fetch('/api/options');
this.options = await resp.json();
this.renderOptions();
}
render() {
this._shadowRoot.innerHTML = `
<style>
.container { /* 样式隔离在这里 */
border: 1px solid #ddd;
padding: 8px;
width: 200px;
font-size: 14px;
}
</style>
<div class="container">
<input type="text" placeholder="${this._placeholder || '请输入'}" />
<ul class="options"></ul>
</div>
`;
}

renderOptions() {
const ul = this._shadowRoot.querySelector('.options');
ul.innerHTML = '';
this.options.forEach(option => {
const li = document.createElement('li');
li.textContent = option.label;
li.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('select', {
detail: option.value
}));
});
ul.appendChild(li);
});
}
}
customElements.define('searchable-select', SearchableSelect);
这个组件可以在任意HTML文件中直接使用:
<searchable-select placeholder="请选择"></searchable-select>
<script>
document.querySelector('searchable-select').addEventListener('select', e => {
console.log('选择了:', e.detail);
});
</script>
是不是很像写一个独立的JS插件?区别在于它是基于浏览器原生机制,并且可以嵌入到任意前端框架中。
遇到的坑和解决方案
1. 性能问题:频繁重渲染影响体验
刚开始的时候,每次输入框变化都触发过滤逻辑并重绘列表,结果在低端设备上卡顿明显。后来优化成节流处理 + 虚拟滚动(只渲染可视区域内的选项),才缓解问题。
const throttle = (fn, delay) => {
let ticking = false;
return function (...args) {
if (!ticking) {
setTimeout(() => {
fn.apply(this, args);
ticking = false;
}, delay);
ticking = true;
}
};
};
// 输入事件绑定
input.addEventListener('input', throttle((e) => {
this.filterAndRender(e.target.value);
}, 200));
2. 样式泄露:Shadow DOM并不总是万能的
有时候外部CSS规则会穿透进来,尤其是字体、动画相关的全局样式。这时候可以通过给Shadow DOM包裹一层命名空间类名或者使用CSS变量来增强控制。
3. 第三方库兼容问题
有些第三方图标库或者工具函数依赖window或特定环境变量,在Web Components中引入需要做特殊处理。建议尽量使用无副作用的纯函数版本库,或者封装包装层。
实际效果与收益
经过几个月的逐步替换,我们主要获得了以下几个方面的提升:
- 组件解耦成功:Web Components让我们把核心功能模块抽象出来,供不同团队以统一的方式引用。
- 样式冲突减少:借助Shadow DOM的作用域隔离,大大减少了样式混乱问题。
- 性能表现良好:相比以前Vue组件动辄几十KB的初始加载,Web Component体积更小,启动更快。
- 维护成本下降:组件升级只需改一处即可全量生效,不再需要各个仓库分别同步更新。
更重要的是,这套架构为未来多技术栈共存奠定了良好的基础。
经验分享与建议
如果你也在考虑是否要用Web Components,这里是我总结的一些经验和建议:
✅ 适合的场景:
- 基础UI组件库(按钮、表单、弹窗等)
- 对性能要求中等,不需要频繁状态变更的功能
- 多技术栈共存的项目(如同时使用Vue和React)
❌ 不太合适的场景:
- 复杂的状态管理需求
- 异步数据频繁更新的组件
- 对兼容性要求极高(旧版IE等)
🛠️ 工具推荐:
- 使用 LitElement 或 Stencil.js 可提高开发效率(它们封装了很多样板代码)
- 用Rollup打包Web Component发布到npm非常方便
- 推荐Chrome DevTools中的“Shadow DOM”面板调试样式隔离问题
💡 小技巧:
- 组件通信尽量使用
CustomEvent,避免暴露过多API - 提供默认值,方便开发者理解props
- Shadow DOM内尽量不要操作DOM,而是用数据驱动视图(可以用简单的模板引擎)
结语:别让技术限制思维边界
Web Components并不是新东西,但它却实实在在帮我们解决了眼前遇到的真实问题。它不是要替代Vue或React,而是作为它们之外的一个补充选项。
有时候我们会习惯性地觉得只有最主流的才是最好的,但实际上真正的好技术,是在合适的场景下发挥了应有的价值。
作为一名前端开发者,我一直坚信:没有银弹,只有不断寻找最适合当下情况的技术组合。而Web Components这次给了我一个新的视角,也让团队更加灵活高效。
希望这篇分享能给你带来一点启发,也欢迎你在评论区留言交流你的组件化开发心得。

评论 0