Web Components:原生组件化开发新趋势
上周五晚上,我刚刷完 LeetCode 第 137 题(别问,问就是跳槽焦虑症晚期),顺手点开 GitHub Trending,突然看到一个叫 shoelace-style 的 Web Components UI 库冲上了榜首。我心里咯噔一下:这玩意儿不是“上古技术”吗?怎么又火了?
作为一个在国企混了快五年、每天准时8点到工位、下班坚决不加班的程序员,我对“新趋势”一向是半信半疑的——毕竟咱们这儿连 jQuery 都还没完全退场呢。但最近被领导安排了一个“微前端兼容性改造”的活儿,要求把几个老系统里的公共组件抽出来复用,还不让引入 React/Vue 这些“重型武器”(理由是“增加维护成本”)。得,这不就是 Web Components 的主场吗?
被逼出来的技术选型
事情是这样的:我们团队负责的内部管理系统有七八个,每个都长得差不多——顶部导航、左侧菜单、表格列表、分页器……但因为历史原因,每个系统都是独立开发的,连按钮样式都能差出三种蓝色。测试同事每次提 bug 都要写“这个按钮在 A 系统是圆角,在 B 系统是直角”,听得我都想替他们写自动化脚本。
产品经理上周开会拍板:“必须统一!下个月上线前搞定!” 我当场就想反问:“您知道前端资源有多紧张吗?” —— 但话到嘴边咽回去了,毕竟国企讲究“和气生财”。
于是开始调研方案:
- 直接 copy-paste 组件代码?不行,改一处要改八处,运维会拿拖鞋追着我打。
- 封装成 npm 包?可以,但每个项目技术栈不同(Angular/React/jQuery 混搭),构建流程对不上。
- 微前端框架?qiankun 太重,而且领导明确说了“不要新依赖”。
这时候 Web Components 跳进我脑子——原生支持、零依赖、跨框架兼容,简直就是为这种“缝合怪”项目量身定制的!
实战:从 Hello World 到线上跑
先说结论:Web Components 真香!但踩坑也不少。
我第一个 demo 是写一个 <my-button>:
class MyButton extends HTMLElement {
constructor() {
super();
// 必须调用 attachShadow 创建 Shadow DOM
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
button {
background: #4CAF50;
border: none;
color: white;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
button:hover { opacity: 0.8; }
</style>
<button><slot></slot></button>
`;
}
}
customElements.define('my-button', MyButton);
在 HTML 里直接用:
<my-button>点击我</my-button>
效果立竿见影!但问题来了:样式隔离是好事,可怎么全局定制主题色? 我总不能让每个系统都重写一遍 CSS 吧?
灵机一动,用 CSS 变量搞定:
/* 在宿主页面定义 */
:root {
--primary-color: #2196F3;
}
组件内部改成:
button {
background: var(--primary-color, #4CAF50); /* 默认值兜底 */
}
完美!运维再也不用担心颜色不一致了(虽然他可能根本不知道发生了什么)。
性能优化:别让 Shadow DOM 成性能杀手
Web Components 最大的优势是封装性,但 Shadow DOM 也会带来额外开销。我拿 Chrome DevTools 的 Performance 面板测了下:
| 场景 | 渲染耗时 (ms) | 内存占用 (MB) |
|---|---|---|
| 普通 div 按钮 | 12 | 45 |
| Web Components 按钮 | 18 | 48 |
差距不大,但当页面有上百个组件时(比如我们的数据表格),就得精打细算了。
我的优化策略:
- 避免在 constructor 里做 heavy lifting —— 所有 DOM 操作移到
connectedCallback - 用 template 标签缓存结构,减少 innerHTML 解析开销
- 事件委托:别给每个子元素绑事件,统一在 shadow root 上监听
关键代码:
// 全局缓存 template
const template = document.createElement('template');
template.innerHTML = `<style>...</style><button><slot></slot></button>`;
class OptimizedButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
// 动态逻辑放这里,比如绑定事件
this.shadowRoot.querySelector('button').addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('my-click'));
});
}
}
实测后,百个按钮渲染时间从 220ms 降到 160ms,内存也稳住了。虽然比不上原生 div,但换来的是无价的可维护性——这波不亏!
面试题预警:面试官最爱问的坑
最近刷题准备跳槽,发现 Web Components 已经悄悄出现在大厂面试题里了。分享几个高频问题:
Q:Web Components 和 React/Vue 组件有什么本质区别?
A:前者是浏览器原生标准,后者是框架层抽象。Web Components 不依赖任何 JS 库,天然跨框架。
Q:Shadow DOM 的封闭模式(closed)有什么用?
A:防止外部 JS 访问内部 DOM(但实际很少用,调试困难,且现代框架基本不需要)。
Q:如何解决 Web Components 的 SEO 问题?
A:服务端渲染(SSR)——可以用 Puppeteer 或专门的工具如 ShadyCSS(不过我们内部系统无所谓 SEO 😅)。
建议大家去 GitHub 搜 web-components-examples,Google 官方仓库里有超多实战案例。我 fork 了一个改造成 TypeScript 版本,star 数不多但自己用得很爽。
兼容性:IE?不存在的!
说到兼容性,我差点翻车。测试同事拿 IE11 跑我们的新页面,直接白屏。打开控制台一看:
SCRIPT5009: 'customElements' 未定义
得,忘了 polyfill。赶紧加上:
<script src="https://unpkg.com/@webcomponents/webcomponentsjs@2/webcomponents-bundle.js"></script>
但领导发话:“内部系统只支持 Chrome 80+,不用管 IE”。我长舒一口气——终于不用跟十年前的技术搏斗了!(感谢公司 IT 部门统一升级浏览器)
主流浏览器支持情况(2023年):
| 浏览器 | 支持情况 |
|---|---|
| Chrome | ✅ 54+ |
| Firefox | ✅ 63+ |
| Safari | ✅ 10.1+ |
| Edge | ✅ 79+ (Chromium版) |
| IE11 | ❌ 需 polyfill |
对于我们这种“技术债高筑但用户可控”的国企项目,简直是天作之合。
最后:为什么我觉得 Web Components 会火?
不是因为它多酷炫,而是它解决了真实世界的痛点:
- 资源有限:不用学新框架,现有团队快速上手
- 渐进式改造:老项目可以一点点替换,不用推倒重来
- 跨团队协作:设计组出一套 Web Components 库,前后端都能直接用
上周我把按钮、输入框、表格封装成组件库,发布到公司私有 npm 仓库。今天早上收到消息:隔壁组已经开始接入了!虽然他们还在用 AngularJS,但 <my-table> 居然跑起来了——那一刻我仿佛看到了前端界的“人类命运共同体”。
当然,Web Components 不是银弹。复杂交互逻辑还是得靠框架,但它绝对是轻量级复用场景的王者。尤其当你被产品经理催着交活、又被领导卡着技术栈的时候,它就是你的救命稻草。
写完这篇博客,已经是晚上9点。赶紧关电脑——双休程序员绝不加班!明天还得早起刷题呢(LeetCode 第 138 题:Copy List with Random Pointer,救命)。
如果你也在折腾 Web Components,欢迎来我 GitHub 仓库 issue 区吐槽:github.com/yourname/web-components-playground (名字是假的,别真点)
P.S. 本文所有代码已通过 Chrome 115 + Firefox 115 实测,IE 用户请自行面壁思过。

评论 0