Web Components:原生组件化开发新趋势
Web Components:原生组件化开发新趋势
记得去年我接手的一个项目,是给公司一个内部的运营管理系统做一次重构。原来的代码结构已经很“年久失修”,Vue 和 jQuery 混杂在一起,组件复用性差得要命。每次加个新功能或者改点样式,都像在拆定时炸弹——不知道哪块儿会炸。当时我在想:有没有一种方式能让我既不依赖框架,又能写出可维护、可复用的组件呢?
就在这时候,我开始接触并尝试使用了 Web Components —— 一个基于浏览器原生标准的技术方案。它不仅帮我解决了模块化的难题,还让我重新认识了前端组件化的本质。今天我想通过这篇文章,和你们分享一下我的实战经验,聊聊它是如何从一个“小众玩意儿”变成我们团队日常开发中不可或缺的一部分的。
背景与挑战:为什么我们需要 Web Components?

这个系统是一个典型的内部管理后台工具,涵盖权限控制、数据报表、任务配置等功能模块。整个系统的用户群体比较固定,主要是产品、客服以及市场同事。之前采用的是 Vue 单页应用(SPA)架构,但也引入了不少 jQuery 插件用于表单验证、弹窗提示等,导致不同页面之间存在大量的重复逻辑和 UI 样式。
随着时间推移,问题逐渐暴露出来:
- 组件难以复用:每个页面都需要复制黏贴大量 HTML + JS 代码,容易出错。
- 样式冲突频发:CSS 全局污染严重,修改一处会影响多个页面。
- 技术栈分散:部分老页面还在用 jQuery,新页面用 Vue,迁移成本高。
- 性能问题:虽然整体体积不大,但多个库叠加加载慢,影响用户体验。
这些问题促使我们考虑一种更通用、轻量、兼容性强的组件化方案。于是,我提出了引入 Web Components 的想法,目标是建立一套可以在多种环境下使用的组件库,无论是 Vue 页面还是纯 HTML 页面都能用上这些组件。
解决方案:Web Components 上手实践

我们决定以最常用的几个组件作为切入点,比如按钮 <custom-button>、模态框 <custom-modal> 和输入框 <custom-input>。目标很明确:把这些组件做成独立、封装良好、样式隔离、行为可控的自定义元素,并能够在任意项目中直接调用,无需依赖任何框架。
Web Components 的核心由三部分组成:
Custom Elements:自定义 HTML 元素Shadow DOM:用于样式和 DOM 封装,避免全局污染HTML Templates:提前定义好模板,提高渲染效率
下面是我写第一个 <custom-button> 时的关键代码:
<!-- index.html -->
<custom-button label="提交" theme="primary"></custom-button>
// custom-button.js
class CustomButton extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
// 定义模板
const template = document.createElement('template');
template.innerHTML = `
<style>
button {
padding: 10px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.primary {
background-color: #4a90e2;
color: white;
border: none;
}
.secondary {
background-color: transparent;
color: #4a90e2;
border: 1px solid #4a90e2;
}
</style>
<button class="${this.getAttribute('theme') || 'primary'}">
${this.getAttribute('label')}
</button>
`;
this.shadow.appendChild(template.content.cloneNode(true));
}
}
customElements.define('custom-button', CustomButton);
这段代码看起来简单,但在初期也踩过不少坑,后面再详细说。
实战踩坑:那些年我们一起绕过的弯路
坑一:Shadow DOM 不支持伪类选择器深度穿透
一开始我们在 Shadow DOM 内部写了 ::before 或 ::after 的伪类来添加图标或装饰效果,结果发现某些浏览器下根本不生效。后来查文档才知道,在 Shadow DOM 中,这些伪类的行为可能会受到限制,必须用特定方式处理,比如:
button::after {
content: " →";
}
有时候甚至得结合外部样式注入或 JavaScript 动态操作。
坑二:样式作用域没理解清楚
虽然 Shadow DOM 天然实现了样式隔离,但我们误以为可以直接从外部修改其内部样式,结果发现完全无效。后来明白了可以通过 CSS 变量的方式传入参数:
:host {
--button-bg: #4a90e2;
}
这样组件外就可以通过修改变量来改变组件主题色。
坑三:生命周期事件绑定时机不对
刚开始没有在 connectedCallback 中做初始化逻辑,而是直接在构造函数里绑定事件,结果出现点击无响应的问题。正确的做法应该是:
connectedCallback() {
this.shadow.querySelector('button').addEventListener('click', () => {
console.log('button clicked');
// 触发自定义事件供父级监听
this.dispatchEvent(new CustomEvent('on-click'));
});
}
否则可能 DOM 还没插入进 Shadow 就绑定了,根本找不到节点。
开发技巧与调试心得
- Chrome DevTools 支持非常好,可以方便地查看 Shadow DOM 结构,调试样式。
- 使用
::part()和::theme()配合浏览器扩展可以对 Shadow DOM 内部做细粒度样式调整。 - 推荐使用 LitElement 等现代库来简化开发流程,特别是如果你需要响应式更新的话,手动操作 DOM 更新效率确实不高。
- 在构建过程中加入 polyfill 兼容旧版浏览器,例如微软 Edge Legacy,虽然现在基本淘汰了,但我们公司部分客户环境还在使用。
成果与收益:真正落地后的变化
经过几个月的迭代,我们把关键的 8 个组件都做了标准化封装,包括表单验证器、分页控件、文件上传等。最终的效果还是很明显的:
- UI 统一度大大提升,所有组件在不同页面表现一致;
- 开发效率显著提高,新需求只要插入标签就能完成组件引用;
- 维护成本大幅下降,样式 bug 几乎只存在于组件内部;
- 跨技术栈兼容性增强,不管是 Vue 页面还是 PHP 渲染的老页面,都可以直接使用;
- 首屏性能优化,因为组件本身不依赖大框架,加载更快。
更重要的是,我们不再为“要不要升级 Vue 到 3”或者“是否换用 React”而纠结,因为底层组件已经解耦了业务框架本身。
我的一些建议
如果你想在项目中尝试 Web Components,我建议你:
1. 从小处着手,不要追求一步到位
先挑两三个高频使用的基础组件进行封装,比如按钮、输入框、弹窗之类。别一开始就想着做一个完整的 UI 库,那只会让你望而却步。
2. 关注浏览器兼容性和降级策略
虽然现代浏览器都已支持大部分特性,但如果你的产品还需要兼容 IE11 以下版本,建议搭配 polyfill 使用,或者权衡是否值得。
3. 合理使用 Shadow DOM,不是万能锁
Shadow DOM 是样式隔离的好帮手,但也不是所有组件都需要用。如果不需要高度封装,普通组件配合 CSS 模块化也是不错的选择。
4. 结合现有框架一起用
Web Components 并不是 Vue、React 的替代品,而是互补的存在。你可以把它当作是组件体系的“原子层”,而框架则负责更高层次的状态管理和交互控制。
5. 写组件文档很重要
哪怕只是注释标明属性含义、用法和示例,这会让后续接手的人少走很多弯路。
总结:组件化是未来,而 Web Components 是基石
回顾过去一年的实践,我觉得 Web Components 给我带来最大的启发就是——前端开发不应该被框架绑架。虽然 Vue、React 等框架各有千秋,但如果我们的基础组件能做到不依赖特定生态,那无疑会大大降低长期维护的成本。
我也相信随着浏览器特性的完善,越来越多公司会将 Web Components 引入到自己的技术栈中。它不一定是“银弹”,但它一定是解决前端组件复用和多技术栈共存问题的一条非常值得探索的路径。
如果你也遇到组件复用难、样式冲突多、技术栈混杂等问题,不妨试试 Web Components。说不定,它也能成为你的“救命稻草”。
最后说句题外话:
技术这条路,其实很多时候没有绝对的“好”与“坏”。Web Components 也许并不是适合每一个项目,但对于一些特定场景,比如企业内部系统、混合技术栈的大型项目来说,它真的是值得一试的“宝藏”。
希望这篇文章能帮你在某个深夜加班的时刻,找到一个新的思路。咱们,一起在路上。

评论 0