Web Components:原生组件化开发新趋势
上周五晚上十点半,我瘫在工位上盯着屏幕上第 37 次刷新的页面,心想:“这破交互效果再不搞定,明天站会又要被 PM 灵魂拷问了。”
坐标北京,通勤一小时打底,海归硕士回国找工作ing——没错,就是我。白天投简历、晚上肝项目,最近接了个小活儿练手,结果一脚踩进了 Web Components 的深水区。
说来惭愧,之前一直用 React 写业务组件,以为 useState + useEffect 就能打天下。但这次客户明确要求“不依赖任何框架”,还得支持 IE11(别问,问就是国企 legacy 系统)。当时我差点想直接甩一句“那你去用 jQuery 吧”,但想想简历上刚写完“精通现代前端架构”,咬咬牙还是硬着头皮上了。
为什么突然对 Web Components 上头?
其实早在去年双11期间,我就在团队内部技术分享会上听过 Web Components 的概念,但当时觉得“这玩意儿连 React 都干不过,有啥好搞的?” 直到最近面试某大厂时被问到:“你们项目里有没有尝试过原生组件化?如何解决跨框架复用问题?” 我支支吾吾半天,最后只能尴尬地承认:“我们全栈 React,没机会用。”
那一刻,我意识到:只会 React,在高级前端岗上已经不够看了。
再加上最近做的这个动画密集型项目——一个可嵌入第三方网站的数据看板,要求轻量、自包含、无外部依赖。产品经理还加了个需求:“用户拖拽图表时要有弹簧回弹效果,像 iOS 那样丝滑。” 我心里一紧:这不就是我最感兴趣的前端动画和交互吗?但用 React 做的话,打包体积动不动就 100KB+,人家只想要一个 <data-chart> 标签就能跑的东西。
于是,Web Components 成了我的救命稻草。
实战踩坑:从“Hello World”到线上事故
刚开始写,我以为 Web Components 就是 customElements.define() + template 字符串拼接。天真如我,第一版代码长这样:
class MyChart extends HTMLElement {
constructor() {
super();
this.innerHTML = '<div>My Awesome Chart</div>';
}
}
customElements.define('my-chart', MyChart);
本地跑得飞起,结果一部署到测试环境,运维大哥发来消息:“你这组件在 Safari 上白屏了。” 我一查,哦,Safari 要求必须先调用 super() 才能操作 this,而且模板不能直接塞 innerHTML(性能差还容易 XSS)。
更惨的是,为了实现那个“弹簧动画”,我手搓了一个基于物理引擎的缓动算法——位置 = 初始位置 + 阻尼 * 速度 + 弹性系数 * 位移。结果因为没做 requestAnimationFrame 节流,CPU 占用飙到 80%,MacBook 风扇狂转,测试同事直接吐槽:“你这组件是矿机吧?”
💡 教训:Web Components 不是“写完就行”,它要求你对 DOM、事件、渲染机制有更深的理解。React 帮你屏蔽的细节,这里全得自己扛。
后来我重构了整个组件结构,用 <template> + shadow DOM 隔离样式,动画部分改用 CSS transition + transform,既省性能又兼容性好。关键代码如下:
const template = document.createElement('template');
template.innerHTML = `
<style>
.chart-container {
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
</style>
<div class="chart-container">
<!-- SVG or canvas here -->
</div>
`;
class DataChart extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
// 组件挂载后初始化数据
this.renderChart();
}
renderChart() {
// 这里调用 D3 或 Canvas 绘图逻辑
}
}
customElements.define('data-chart', DataChart);
和 React 怎么共存?别怕,它们能谈恋爱
很多人担心:“我项目全是 React,还能用 Web Components 吗?” 当然能!我们团队就在一个大型后台系统中混合使用。
具体做法是:把 Web Components 当作“原子组件”封装复杂交互,React 负责状态管理和业务逻辑。比如我们的日期选择器,用 Web Components 实现弹窗、动画、键盘导航,然后在 React 中这样调用:
function App() {
const [date, setDate] = useState('');
return (
<div>
{/* Web Component 作为普通 HTML 元素使用 */}
<date-picker
value={date}
onDateChange={(e) => setDate(e.detail.value)}
></date-picker>
</div>
);
}
注意两点:
- 自定义事件要用
CustomEvent触发,并带上detail字段; - React 无法直接监听 Web Components 的自定义事件,得用
ref手动 addEventListener(或者用@lit-labs/react这类桥接库)。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 纯 Web Components | 无框架依赖、体积小、跨项目复用 | 开发效率低、生态弱 |
| Web Components + React | 复用性强、渐进式迁移 | 事件通信稍麻烦、需额外桥接 |
我们最终选择了混合模式——核心 UI 库用 Web Components 写,业务页面用 React。上线三个月,零兼容性问题,连 IE11 用户都夸“加载快多了”。
性能优化:别让 Shadow DOM 成为性能黑洞
Shadow DOM 虽好,但滥用会导致样式计算爆炸。我曾经在一个组件里嵌套了三层 Shadow Root,结果 Lighthouse 评分掉到 40 分,产品经理看到报告直接问我:“是不是偷懒没优化?”
后来我学乖了:
- 避免深层嵌套:一个组件只用一层 Shadow DOM;
- CSS 变量代替内联样式:通过
:host暴露可配置主题; - 懒加载非关键资源:比如图标、字体等放到
connectedCallback之后异步加载。
还有一个隐藏技巧:用 slot 实现内容分发,比动态插入 innerHTML 更高效。比如:
<modal-dialog>
<span slot="header">确认删除?</span>
<button slot="footer" onclick="confirm()">确定</button>
</modal-dialog>
组件内部用 <slot name="header"> 即可接收内容,无需 JS 操作 DOM。
调试技巧:Chrome DevTools 已经很香了
以前觉得调试 Web Components 很痛苦,其实 Chrome 早就支持了:
- Elements 面板可以直接展开 Shadow Root;
- Styles 面板会显示
:host和::slotted样式; - Console 里可以用
$0.shadowRoot快速访问当前选中元素的影子根。
另外,强烈推荐用 Lit(不是 LIT,是 Lit)作为 Web Components 的开发库。它提供了响应式更新、模板绑定、装饰器语法,写起来几乎像 Vue,但输出仍是标准 Web Components。我们的实战经验表明,用 Lit 开发效率提升 40%,且 bundle size 仅 5KB 左右。
最后:值不值得投入?
回到最初的问题:Web Components 是未来吗?
我的结论是:它不是 React 的替代品,而是补充。如果你的项目需要:
- 跨框架复用(Vue/React/Angular 通吃)
- 极致轻量化(比如嵌入第三方网站)
- 长期维护的 UI 原子库
那么 Web Components 绝对值得投入。尤其在国内大厂推进“微前端”和“Design System”的背景下,原生组件化能力越来越重要。
当然,别指望它一夜爆火。毕竟连 React 团队都说:“We don’t see Web Components and React as competitors.”(我们不认为 Web Components 和 React 是竞争关系。)
上周我把这个 data-chart 组件打包成 npm 包,发到公司内部 registry,结果隔壁组 PM 主动来找我:“听说你做了个不依赖框架的图表?我们新项目能用吗?”
那一刻,通勤一小时的疲惫、深夜 debug 的焦躁,全都值了。
P.S. 如果你也正在从 React 转向原生组件化,欢迎交流。简历还在投,前端岗,北京,擅长动画与交互,Web Components 实战经验+1。😉

评论 0