Web Components:原生组件化开发新趋势
Web Components:在复杂前端生态中寻找“原生”的答案
几年前,我所在的团队接手了一个全新的项目——一个企业级的内部管理平台。这个平台需要集成多个系统模块,包括用户管理、权限配置、数据可视化、日志监控等。面对如此复杂的业务逻辑和UI需求,我们最初的选择是使用当时主流的 React 技术栈来搭建整套系统。
然而,在开发过程中我们逐渐发现了一些棘手的问题:
- 组件复用困难:不同模块间存在大量重复 UI 元素(如输入框、按钮、弹窗),但由于它们处于不同的 React 项目中,无法直接共享组件。
- 团队协作障碍:前端团队被拆分成了多个小组,各自维护独立的 React 应用,由于技术方案不统一,导致最终整合时问题频发。
- 第三方库依赖过重:虽然 React 社区丰富,但引入过多第三方库导致构建体积臃肿,影响首屏加载速度。
- 渲染性能瓶颈:随着组件层级加深,React 的虚拟 DOM 更新机制在某些情况下出现了卡顿现象。
这些问题让我们意识到:或许我们需要一种更轻量、更通用、更接近浏览器原生能力的方式来组织我们的组件结构。
于是,我们开始将目光投向 Web Components —— 这个早在 2011 年就被提出的技术标准,在现代浏览器中已经逐步成熟,并且得到了各大框架的支持。
转向 Web Components:一次大胆的尝试

Web Components 是一组浏览器原生 API 的统称,主要包括:
- Custom Elements:可以定义自己的 HTML 标签
- Shadow DOM:提供封装样式与结构的能力
- HTML Templates:声明式的可复用模板
- ES Modules:基于 JavaScript 模块化加载机制
这些特性让开发者可以创建真正的“原生”组件,而无需依赖任何框架。
我们决定在新开发的日志监控模块中进行一次小规模试点。目标很明确:用 Web Components 来封装一个可复用的表格组件,能够在不同的项目甚至不同技术栈中被引用。
实战中的设计与实现

我们在项目中创建了一个名为 <custom-data-table> 的自定义元素,其主要功能是渲染一个支持排序、筛选、分页的表格,并支持传入 JSON 数据动态渲染内容。
下面是简化版的核心代码示例:
class CustomDataTable extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// 创建模板内容
const template = document.createElement('template');
template.innerHTML = `
<style>
table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; border: 1px solid #ccc; text-align: left; }
.sort-button { cursor: pointer; }
</style>
<table>
<thead>
<tr id="header-row"></tr>
</thead>
<tbody id="body"></tbody>
</table>
`;

this.shadowRoot.appendChild(template.content.cloneNode(true));
this.headers = [];
this.rows = [];
}
connectedCallback() {
this.render();
}
static get observedAttributes() {
return ['data'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'data') {
this.rows = JSON.parse(newValue);
this.render();
}
}
render() {
const headerRow = this.shadowRoot.getElementById('header-row');
const body = this.shadowRoot.getElementById('body');
// 清空并重新渲染
headerRow.innerHTML = '';
body.innerHTML = '';
// 生成表头
if (this.rows.length > 0) {
Object.keys(this.rows[0]).forEach(key => {
const th = document.createElement('th');
th.textContent = key;
headerRow.appendChild(th);
});
}
// 生成行数据
this.rows.forEach(row => {
const tr = document.createElement('tr');
Object.values(row).forEach(value => {
const td = document.createElement('td');
td.textContent = value;
tr.appendChild(td);
});
body.appendChild(tr);
});
}
}
customElements.define('custom-data-table', CustomDataTable);
接下来我们就可以在任意 HTML 文件中这样使用它:
<custom-data-table data='[{"name":"Alice","age":25},{"name":"Bob","age":30}]'></custom-data-table>
这简直太棒了!它不需要任何框架依赖,也不需要 npm 安装,开箱即用。
在实际开发中踩过的那些坑

当然,理想很美好,现实也很骨感。我们在这个过程中也遇到了不少挑战。
1. 样式作用域控制
刚开始我们以为 Shadow DOM 天然隔离了样式,但实际上,有些全局 CSS 还是会通过继承影响组件表现。比如 a 标签的颜色设置、字体大小等。
✅ 解决办法: 我们在组件模板的
<style>中主动重置了一些基础样式,类似:*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }同时在主应用中也避免使用
!important或者全局覆盖性太强的样式类。
2. 事件通信机制
如何将组件内部事件(例如点击某一行)通知给外部呢?我们知道不能直接挂载 onclick 到子元素上,因为这些 DOM 是在 Shadow Root 内部的。
✅ 解决办法: 使用
dispatchEvent主动触发事件。
const rowClickEvent = new CustomEvent('row-click', {
detail: { rowData: row },
bubbles: true,
composed: true
});
tr.dispatchEvent(rowClickEvent);
然后外部监听:
<custom-data-table onrow-click="handleTableRowClick"></custom-data-table>
或者 JS 监听方式:
document.querySelector('custom-data-table').addEventListener('row-click', e => {
console.log('Row clicked:', e.detail.rowData);
});
3. 兼容性和 Polyfill
并不是所有浏览器都完美支持 Web Components。尤其是一些老旧的内核浏览器(比如 IE11 及以下版本),在客户环境中仍有一定比例用户群体。
✅ 解决方案: 我们引入了 Google 提供的 webcomponents.js polyfill,并通过 Webpack 配置实现了按需加载。
import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js';
import '@webcomponents/webcomponentsjs/webcomponents-bundle.js';
同时我们在构建流程中添加了对老浏览器的降级处理逻辑。
收益与思考
经过三个月的实践与打磨,我们将 Web Components 逐步推广到了整个项目中。结果出乎意料的好:
- 跨项目组件复用变得简单:一个组件只需要打包成单独的 JS 文件,即可在其他项目中以
<script>引入; - 构建包体积显著下降:相比之前引入 React + 系列插件的方式,Web Components 组件本身的 JS 文件平均只在几 KB;
- 团队协作更加顺畅:各个前端组不再受限于技术选型,都可以无缝接入相同的组件体系;
- 性能提升明显:页面加载快了不少,组件渲染效率更高,尤其在低端设备或网络较差的情况下体验更好。
更重要的是,这种原生化的组件开发方式,让开发者回归到了“写 HTML/CSS/JS 的本质”,而不是沉迷于各种框架的语法糖和抽象层中。
如果你也想尝试,这里有些建议送给你
如果你正考虑是否要在项目中引入 Web Components,我的建议如下:
✅ 适用场景推荐
- 需要跨项目复用组件
- 希望减小构建体积
- 不想绑定特定技术栈
- 追求极致的性能表现
❗ 注意事项
- 不要忽视样式隔离问题,尤其是在复杂项目中。
- 对旧浏览器支持要做好 Polyfill 和降级处理。
- 要有一套清晰的组件命名规范,避免冲突。
- 可借助构建工具(如 Lit、Stencil、Open WC)提高开发效率。
🛠 开发工具推荐
- LitElement / Lit:Google 推出的轻量级 Web Components 库,提供了良好的响应式编程模型;
- Vue / React 渲染器:可以通过一些中间适配器,将 Web Components 嵌入到 Vue 或 React 项目中;
- DevTools 调试技巧:Chrome DevTools 支持查看 Shadow DOM 结构,调试更方便;
- Storybook for Web Components:可以为你的 Web Components 构建一个可视化的组件库文档。
结语:原生的力量从未过时
Web Components 让我重新认识了“原生”的价值。它不是对现有框架的替代,而是提供了一种更轻量、更自由的组件构建思路。在我参与的多个项目中,无论是从性能、可维护性还是团队协作角度来看,它都展现出了强大的生命力。
技术总是在不断演变,但我相信:真正好的架构,往往是对底层能力的合理抽象与组合。
如果你还没尝试 Web Components,不妨从一个小组件开始,感受一下“原生的力量”。它可能不会马上惊艳你,但它一定会让你走得更远。

评论 0