Web Components:原生组件化开发新趋势
大家好,我是个刚毕业的大专计算机应届生,在深圳一家做电商 SaaS 的小厂干前端。虽然学历被不少 HR 嘲过“简历筛不过”,但靠着自学 + 死磕 + GitHub 上攒的几个小项目,居然在去年秋招拿到了 offer。入职后天天和 Vue、React 打交道,直到上周五晚上加班到十一点,才真正对 Web Components 这个“冷门选手”有了点感觉。
事情是这样的:我们公司最近要给客户做一套可嵌入第三方网站的营销插件(比如弹窗、倒计时、抽奖组件),产品经理拍脑袋说:“这个必须能直接用 <script> 引入,不能依赖 React/Vue,人家后端可能就只会写 PHP!” 当时我就懵了——不靠框架怎么搞组件化?总不能真手搓 jQuery 吧?
为什么 Web Components 突然香了?
其实 Web Components 这个概念早在 2013 年就提出来了,但一直不温不火,大家都觉得“有 React 谁还用你啊”。但随着微前端、跨技术栈集成、低代码平台这些需求越来越多,它的优势反而凸显出来了:
- 原生支持:不用打包、不用构建工具,浏览器天生就能跑
- 框架无关:不管是 Vue、React、Angular,甚至纯 HTML 页面都能塞进去
- 隔离性强:Shadow DOM 天然防样式污染,再也不怕 PM 说“你们前端改个样式把整个页面搞崩了”
对我们这种小团队来说,最爽的是——交付简单!以前给客户发个组件包,还得附带 package.json、webpack.config.js,现在直接丢一个 .js 文件,人家一行 <script src="my-widget.js"></script> 就完事了,后端同学都夸我们“终于懂人话了”。
动手试试:从零写个倒计时组件
我拿上周做的“双11倒计时”练手,目标很简单:传入一个截止时间,自动显示“X天X小时X分X秒”,还能自定义样式。
先看核心结构(别慌,代码真不多):
// countdown-widget.js
class CountdownWidget extends HTMLElement {
constructor() {
super();
// 创建 Shadow DOM,隔离样式
this.attachShadow({ mode: 'open' });
}
// 属性监听(类似 Vue 的 props)
static get observedAttributes() {
return ['endtime'];
}
// 属性变化回调
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'endtime') {
this.render();
this.startCountdown();
}
}
render() {
const endTime = new Date(this.getAttribute('endtime')).getTime();
const now = Date.now();
const diff = Math.max(0, endTime - now);
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
this.shadowRoot.innerHTML = `
<style>
.countdown { font-family: Arial; color: #ff6b6b; }
.number { display: inline-block; padding: 4px 8px; background: #f8f9fa; margin: 0 2px; border-radius: 4px; }
</style>
<div class="countdown">
<span class="number">${days}</span>天
<span class="number">${hours.toString().padStart(2, '0')}</span>时
<span class="number">${minutes.toString().padStart(2, '0')}</span>分
<span class="number">${seconds.toString().padStart(2, '0')}</span>秒
</div>
`;
}
startCountdown() {
clearInterval(this.timer);
this.timer = setInterval(() => {
this.render();
}, 1000);
}
disconnectedCallback() {
// 组件卸载时清理定时器,防止内存泄漏
clearInterval(this.timer);
}
}
// 注册自定义元素
customElements.define('countdown-widget', CountdownWidget);
用起来就更简单了:
<countdown-widget endtime="2023-11-11T00:00:00Z"></countdown-widget>
<script src="./countdown-widget.js"></script>
上线那天,运维大哥居然没找我麻烦(要知道他上次因为 bundle.js 太大骂了我半小时),测试妹子也只报了一个 bug:“倒计时到 0 之后还在闪”。我一看,哦,Math.max(0, ...) 漏了,赶紧 hotfix。搞定那一刻,真的想请自己喝杯蜜雪冰城。
踩坑实录:别信 MDN 示例全是对的
虽然 Web Components 看着清爽,但实际开发还是有不少坑:
1. 样式穿透?不存在的!
Shadow DOM 的最大好处是样式隔离,但有时候你又希望外部能定制样式。这时候可以用 CSS Custom Properties(CSS 变量):
// 在组件内部
this.shadowRoot.innerHTML = `
<style>
.countdown {
color: var(--countdown-color, #ff6b6b);
font-size: var(--countdown-font-size, 16px);
}
</style>
...
`;
外部使用时:
<style>
countdown-widget {
--countdown-color: purple;
--countdown-font-size: 20px;
}
</style>
<countdown-widget endtime="..."></countdown-widget>
这招比 ::part() 或 ::slotted 兼容性好太多,IE 我们早就不伺候了(老板说客户最低要求 Chrome 70+)。
2. 生命周期别乱搞
connectedCallback 和 disconnectedCallback 看似简单,但如果用户频繁挂载/卸载组件(比如 SPA 路由切换),很容易造成内存泄漏或重复渲染。一定要在 disconnectedCallback 里清理定时器、事件监听器。
3. 构建工具兼容性
我们项目还是用 Vue 2 写主站,但 Web Components 是独立构建的。我用 Vite 打包单个 JS 文件,配置贼简单:
// vite.config.widget.js
export default {
build: {
lib: {
entry: 'src/countdown-widget.js',
name: 'CountdownWidget',
fileName: 'countdown-widget',
formats: ['es']
},
rollupOptions: {
output: {
dir: 'dist/widget'
}
}
}
}
执行 vite build --config vite.config.widget.js,直接输出干净的 ES Module,连 Babel 都省了(现代浏览器原生支持 class 语法)。
性能 & 兼容性:真能上生产吗?
我知道你在想:“这玩意儿性能行不行?老浏览器咋办?”
先说结论:现代项目完全可用。
| 特性 | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| Custom Elements | 54+ | 63+ | 10.1+ | 79+ |
| Shadow DOM | 53+ | 63+ | 10+ | 79+ |
| Template | 26+ | 22+ | 7.1+ | 13+ |
数据来自 Can I Use。我们客户基本都是企业级 SaaS,浏览器版本普遍较新,IE?早就进博物馆了好吗!
至于性能,实测加载一个 5KB 的 Web Component,比引入整个 React(~40KB)快多了。而且无虚拟 DOM 开销,直接操作真实 DOM,对简单交互(比如按钮、表单、展示型组件)反而更高效。
对“简历焦虑”的一点思考
作为大专生,我一度特别焦虑:别人简历上写着“精通微前端架构”、“主导 Web Components 落地”,我连 Webpack 原理都说不清。但这次实战让我明白:技术不分高低,能解决问题的就是好技术。
Web Components 虽然“古老”,但在特定场景下就是比框架更合适。而且掌握它,能让你在面试时多一个谈资——“我们用原生组件解决了跨框架集成问题”,HR 看了都觉得你“有架构思维”(虽然我只是个切图仔)。
更重要的是,它让我意识到:前端的核心不是 API 背诵,而是 理解浏览器本身。Shadow DOM、Custom Elements、Template,这些都是浏览器原生能力,学了永远不会过时。
最后:别被“新趋势”绑架
Web Components 不是银弹。如果你在做一个复杂的管理后台,该用 React 还是用 React;但如果是做可复用的 UI 插件、嵌入式小工具,它绝对是被低估的利器。
上周我把这个倒计时组件打包成 NPM 包(名字就叫 @mycompany/countdown-widget),结果隔壁组做 CRM 的后端大哥直接 npm install + <script type="module"> 引入,三分钟搞定。他发了个微信表情包:“前端终于干了件人事”。
那一刻,我觉得熬的夜都值了。
P.S. 如果你也想试试 Web Components,推荐两个工具:
- Lit:轻量级库,帮你省掉模板字符串拼接的痛苦
- VSCode 插件 "Custom Elements Manifest":支持 Web Components 的智能提示,装了它写
attributeChangedCallback再也不怕拼错属性名
P.P.S. 简历上别写“精通 Web Components”,写“熟练应用 Web Components 解决跨技术栈组件复用问题”——听起来更像个人类 😅

评论 0