⏳ 一个等了十年的「元年」
如果你关注前端开发超过五年,你应该记得 Web Components 第一次被提出是在 2011 年。Google 的 Polymer 项目在 2013 年试图推动它,但当时浏览器支持极差,生态几乎为零,折腾了几年最终还是无疾而终。
之后的十年里,前端世界被 React、Vue、Angular 三大框架割据。每个框架都有自己的组件模型、自己的生态、自己的「正确写法」。你想共享一个 UI 组件?要么三个框架各写一份,要么上个 iframe。
但 2026 年不一样了。Web Components 终于等到了它的时代。
📊 发生了什么变化?
1. 浏览器原生支持终于完整
现在的 Chrome、Firefox、Safari、Edge 对 Web Components 的四个核心技术——Custom Elements、Shadow DOM、HTML Templates、ES Modules——已经实现了完全一致的支持。
// 定义一个 Web Component —— 不需要任何框架class UserAvatar extends HTMLElement { static get observedAttributes() { return ['name', 'avatar-url', 'size']; }
constructor() { super(); this.attachShadow({ mode: 'open' }); }
connectedCallback() { this.render(); }
attributeChangedCallback(name, oldVal, newVal) { if (oldVal !== newVal) this.render(); }
render() { const name = this.getAttribute('name') || 'User'; const size = parseInt(this.getAttribute('size') || '40'); const avatarUrl = this.getAttribute('avatar-url');
this.shadowRoot.innerHTML = ` <style> :host { display: inline-flex; align-items: center; gap: 8px; } .avatar { width: ${size}px; height: ${size}px; border-radius: 50%; background: linear-gradient(135deg, #667eea, #764ba2); display: flex; align-items: center; justify-content: center; color: white; font-size: ${size * 0.4}px; font-weight: 600; overflow: hidden; } .name { font-size: 14px; color: var(--text-color, #333); } </style> <div class="avatar"> ${avatarUrl ? `<img src="${avatarUrl}" style="width:100%;height:100%;object-fit:cover">` : name[0]} </div> <span class="name">${name}</span> `; }}
customElements.define('user-avatar', UserAvatar);然后任何框架都能用它:
<!-- React 中使用 --><UserAvatar name="张三" size="48" />
<!-- Vue 中使用 --><user-avatar name="张三" size="48"></user-avatar>
<!-- Angular 中使用 --><user-avatar [attr.name]="'张三'" [attr.size]="'48'"></user-avatar>
<!-- 原生 HTML 中使用 --><user-avatar name="张三" size="48"></user-avatar>看到没有?同样的组件,所有框架都能用。这不比在 React 里写一遍、Vue 里再写一遍香多了?
2. Lit 让 Web Components 开发体验飞升
以前写 Web Components 体验很痛苦——没有响应式状态管理、模板语法原始、Shadow DOM 的样式隔离让调试困难。现在好了,Lit 框架把这些问题都解决了。
// 使用 Lit 开发 Web Components —— 体验跟 React/Vue 差不多import { LitElement, html, css, property } from 'lit';
export class DataTable extends LitElement { static styles = css` :host { display: block; font-family: var(--font-family, system-ui); } table { width: 100%; border-collapse: collapse; } th { background: var(--table-header-bg, #f5f5f5); padding: 12px 16px; text-align: left; font-weight: 600; cursor: pointer; user-select: none; } th:hover { background: var(--table-header-hover-bg, #eee); } td { padding: 10px 16px; border-bottom: 1px solid var(--table-border, #e0e0e0); } .sorted-asc::after { content: ' ▲'; } .sorted-desc::after { content: ' ▼'; } .loading { text-align: center; padding: 40px; color: #999; } `;
@property({ type: Array }) data: Record<string, any>[] = []; @property({ type: Array }) columns: ColumnDef[] = []; @property({ type: Boolean }) loading = false;
private sortKey: string | null = null; private sortDir: 'asc' | 'desc' = 'asc';
private handleSort(key: string) { if (this.sortKey === key) { this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc'; } else { this.sortKey = key; this.sortDir = 'asc'; } this.requestUpdate(); // 触发重新渲染 }
private getSortedData() { if (!this.sortKey) return this.data; return [...this.data].sort((a, b) => { const va = a[this.sortKey!]; const vb = b[this.sortKey!]; return this.sortDir === 'asc' ? String(va).localeCompare(String(vb)) : String(vb).localeCompare(String(va)); }); }
render() { if (this.loading) return html`<div class="loading">加载中...</div>`;
const sortedData = this.getSortedData();
return html` <table> <thead> <tr> ${this.columns.map(col => html` <th @click=${() => this.handleSort(col.key)} class=${this.sortKey === col.key ? `sorted-${this.sortDir}` : ''} > ${col.label} </th> `)} </tr> </thead> <tbody> ${sortedData.map(row => html` <tr> ${this.columns.map(col => html` <td>${row[col.key]}</td> `)} </tr> `)} </tbody> </table> `; }}
customElements.define('data-table', DataTable);这个组件相当于一个「框架无关的 Ant Design Table」。任何技术栈的项目都能用,而且功能和 API 设计完全不输框架自带的 Table 组件。
3. 主流组件库开始拥抱 Web Components
2026 年最显著的变化是——既有的框架组件库开始推出 Web Components 版本。
- Shoelace 早就以 Web Components 为核心,现在已经到了 v3.0,拥有 60+ 高质量组件
- FAST(微软)提供了完整的 Web Components 设计系统
- Ionic 的所有移动端组件都是 Web Components,现在桌面端也在迁移
- Material Web(Google)推出了基于 Lit 的 Web Components 版本
# 安装 Shoelace —— 在任何框架中使用npm install @shoelace-style/shoelace
# 导入组件(无需框架适配器)import '@shoelace-style/shoelace/dist/components/button/button.js';import '@shoelace-style/shoelace/dist/components/dialog/dialog.js';import '@shoelace-style/shoelace/dist/components/input/input.js';<!-- 在任何页面中使用 --><sl-button variant="primary" @click=${handleClick}> 提交订单</sl-button>
<sl-input label="用户名" placeholder="请输入用户名" clearable></sl-input>
<sl-dialog label="确认删除" open=${showDeleteDialog}> 确定要删除这条记录吗? <sl-button slot="footer" variant="danger" @click=${confirmDelete}> 确认删除 </sl-button> <sl-button slot="footer" @click=${cancelDelete}> 取消 </sl-button></sl-dialog>🏗️ 微前端的最佳伴侣
Web Components 在微前端架构中尤其有用。当你的应用被拆分成多个独立部署的微应用(可能用不同的框架开发),共享 UI 组件一直是个难题——直到 Web Components 出现。
// 微前端 Shell 应用 —— 使用 Web Components 作为共享组件层// 无论子应用是 React、Vue 还是 Angular,组件都是通用的
// 共享的组件通过 CDN 加载import 'https://cdn.design-system.company.com/shell-components.js';
// 在 React 子应用中使用function OrderList() { return ( <div> <ds-header title="订单管理" user={currentUser} /> <ds-table data={orders} columns={orderColumns} onRowClick={handleRowClick} /> <ds-pagination current={page} total={totalPages} onChange={setPage} /> </div> );}
// 在 Vue 子应用中使用——同一套组件<template> <div> <ds-header :title="'订单管理'" :user="currentUser" /> <ds-table :data="orders" :columns="orderColumns" /> <ds-pagination :current="page" :total="totalPages" /> </div></template>这解决了一个长期困扰微前端项目的核心问题:组件隔离和跨框架共享。
🔮 Web Components 的局限
作为一个在前线摸爬滚打的开发者,我不能只吹不黑。Web Components 还有一些现实问题:
- 表单集成很痛苦:Web Components 的 Shadow DOM 会让原生表单的
form.elements遍历失效,需要额外处理。 - SSR 支持参差不齐:在服务端渲染场景中,Web Components 需要在客户端注水,不像 React/Vue 组件那样可以无缝 SSR。
- 生态仍在追赶:虽然进步很大,但 Web Components 的插件、工具、调试体验还远不如 React 生态成熟。
- 包体积问题:如果用 Lit,基础库大约 15KB gzip,比纯框架组件重一些,但对大多数项目来说可以接受。
💭 我的判断
Web Components 不会取代 React、Vue 或 Angular——它们解决的是不同层面的问题。框架是应用开发体验的工具,Web Components 是跨框架共享组件的标准。
在 2026 年,我推荐的技术选型策略是:
- 应用内部:用你喜欢的框架(我选 React),享受框架的生态和开发体验
- 跨应用/跨团队:用 Web Components 封装共享组件(设计系统、基础 UI 库),确保在任何框架中都能用
这不是一个「二选一」的问题,而是一个「各自发挥优势」的问题。Web Components 等了十年才「成年」,但事实证明——好饭不怕晚。
🎨 实战:搭建一个框架无关的设计系统
说了这么多理论,我们动手搭建一个真实可用的基础组件库——Design System 的按钮和输入框组件。这个例子可以让你立刻感受到 Web Components 的威力。
第一步:选择技术栈
我们选用 Lit 来开发组件。Lit 是目前 Web Components 生态中最成熟、体验最好的框架,由 Google 维护,代码体积仅 15KB(gzip)。
# 初始化项目npm init -ynpm install lit @lit/reactive-elementnpm install -D typescript @web/dev-server第二步:创建基础 Button 组件
import { LitElement, html, css, property } from 'lit';
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';export type ButtonSize = 'sm' | 'md' | 'lg';
export class DsButton extends LitElement { static styles = css` :host { display: inline-block; } button { border: none; border-radius: 6px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: inline-flex; align-items: center; gap: 8px; } button:disabled { opacity: 0.5; cursor: not-allowed; } .primary { background: #4f46e5; color: white; } .primary:hover:not(:disabled) { background: #4338ca; } .secondary { background: #e0e7ff; color: #4f46e5; } .danger { background: #ef4444; color: white; } .ghost { background: transparent; color: #374151; } .ghost:hover:not(:disabled) { background: #f3f4f6; } .sm { padding: 6px 12px; font-size: 13px; } .md { padding: 8px 16px; font-size: 14px; } .lg { padding: 12px 24px; font-size: 16px; } `;
@property({ type: String }) variant: ButtonVariant = 'primary'; @property({ type: String }) size: ButtonSize = 'md'; @property({ type: Boolean }) disabled = false;
render() { return html` <button class="${this.variant} ${this.size}" ?disabled="${this.disabled}" > <slot name="icon"></slot> <slot></slot> </button> `; }}
customElements.define('ds-button', DsButton);第三步:在任何框架中使用
组件一旦定义好,就可以在任何项目中使用了:
<!-- React 项目 --><ds-button variant="primary" size="lg" @click=${handleSave}> <span slot="icon">💾</span> 保存更改</ds-button>
<!-- Vue 项目 --><ds-button variant="danger" :disabled="isDeleting"> <span slot="icon">🗑️</span> 删除项目</ds-button>
<!-- 纯 HTML 页面 --><ds-button variant="secondary" size="sm"> 取消</ds-button>
<!-- Angular 项目 --><ds-button variant="ghost" (click)="onCancel()"> 返回首页</ds-button>这个设计系统可以在你的所有项目中使用,无论它们是用什么框架写的。这种「一次编写,到处使用」的体验,正是 Web Components 的魅力所在。
当然,这只是一个基础示例。一个完整的 Design System 还需要考虑主题定制(使用 CSS Custom Properties)、国际化(通过 Slots 传递文案)、无障碍(ARIA 属性)等更多细节。但核心思路是一样的:用 Web Components 封装业务无关的 UI 逻辑,让每个框架团队都能用自己熟悉的方式消费这些组件。
从 2026 年开始,越来越多的团队正在采用这种架构。如果你也在维护一个跨框架的大型产品,不妨试试这种方案。
🔬 Web Components 在大型企业中的真实应用案例
光讲技术和理念不够有说服力,我来分享两个我亲眼见证过的真实案例,让大家看看 Web Components 在大型企业中的实际效果。
案例一:某跨国电商的跨框架设计系统
我认识的一个朋友在一家规模不小的电商公司做前端架构师。他们的前端团队有 80 多人,分布在三个产品线。历史原因造成了技术栈碎片化——一个产品线用 React,一个用 Vue,还有一个古老的 AngularJS 应用正在逐步迁移。
2019 年的时候,这三个产品线有三个不同的按钮组件、三个不同的弹窗组件、三个不同的表单组件。设计规范改了,三个团队各改各的,经常出现不一致。到了 2022 年他们尝试用 Web Components 统一 Design System,但当时浏览器支持和工具链都不成熟,折腾了半年效果不佳。
2026 年初他们重新启动了 Web Components 项目,这次完全不同了。Lit 的成熟、浏览器支持的完善、Shoelace 等开源组件库的可用性,让他们的迁移非常顺利。用三个月时间搭建了基础组件库,覆盖了按钮、输入框、选择器、表格、弹窗、分页等 40 多个常用组件。所有组件都是用 Lit 写的 Web Components,三个产品线直接引用同一个 npm 包,使用方式完全一致。
结果是:设计一致性从 65% 提升到了 98%,新组件的开发周期从两周缩短到三天,维护成本降低了约 60%。更重要的是,AngularJS 应用的组件终于可以用上新设计系统的组件了——不需要等迁移完成就能享受到统一设计的好处。
案例二:某 SaaS 产品的微前端改造
另一个是 SaaS 产品做微前端改造的例子。他们的产品由十几个独立的功能模块组成,每个模块由不同的团队维护,独立开发、独立部署。微前端容器用 Module Federation 做,但共享组件库一直是个难题。
2025 年他们试过用 React 写共享组件库,然后每个微前端子应用都打包一份 React。结果 bundle size 爆炸——用户打开页面要加载三次 React 运行时。后来改用 Web Components 写共享组件后,组件库变成了框架无关的纯 HTML 标签,每个子应用不需要再打包框架运行时。bundle size 下降了 40%,首屏加载时间减少了 35%。
这个案例特别能说明 Web Components 在微前端架构中的独特价值:它从根本上解决了「共享代码」和「运行时隔离」之间的矛盾。组件用 Web Components 封装后,每个子应用只需要引用组件的 HTML 标签,不需要关心它背后的实现框架是什么。
🎯 我的最终建议
2026 年做前端技术选型,我的建议可以概括为一句话:用框架写应用,用 Web Components 写组件库。
这不是「弃框架投 Web Components」的叙事。React 和 Vue 在应用开发中仍然有巨大价值——它们的生态、工具链、社区支持都是目前 Web Components 无法比拟的。但是,当你的组件需要被多个项目、多个框架、多个团队复用时,Web Components 就是更好的选择。
具体的决策建议如下:
- 如果你在做一个独立的单体应用,所有组件都在同一个项目中——用你喜欢的框架写组件就好,不需要上 Web Components
- 如果你在维护一个设计系统,需要被多个项目复用——用 Web Components 封装基础组件,然后在各个框架中消费它们
- 如果你在做微前端架构,多个子应用需要共享 UI——Web Components 是目前最优雅的解决方案
- 如果你的技术栈中包含非主流框架或者老旧框架——Web Components 可以帮助你延长它们的寿命,同时逐步过渡到新框架
Web Components 用了十年的时间完成了从「理念」到「可用」再到「好用的飞跃」。2026 年,它不再是那个「未来的技术」,而是已经成为每个前端工程师工具箱中一个趁手的工具。希望这篇文章能帮你更好地理解它、使用它,并在合适的场景中发挥它的价值。
💭 一些个人的思考
写到这里,我想分享一个关于 Web Components 的个人感悟。
我在 2015 年第一次接触 Web Components 的时候还是个刚入行的前端新手,当时 Polymer 项目轰轰烈烈,很多人都说「Web Components 要统治前端了」。结果呢?浏览器兼容性支离破碎,性能糟糕,生态几乎为零。从 2015 到 2020 年,Web Components 基本上处于一个「理论上很好,实际上没法用」的尴尬状态。中间我也尝试过几次,每次都铩羽而归。
但到了 2022 年之后,情况开始慢慢变化。Lit 的出现让开发体验大幅改善,Safari 终于补齐了 Shadow DOM 的兼容性问题,主流组件库开始推出 Web Components 版本。这些变化不是一夜之间发生的,而是社区一点一点积累和推动的结果。
这件事给我的启发是:在技术行业里,一个「早」未必比「巧」好。很多技术在提出的时候非常超前,但当时的生态和基础设施根本没有准备好。真正成功的技术,往往不是最先提出来的那个,而是在合适的时机以合适的方式重新出现的那个。Web Components 在 2011 年被提出、2015 年被炒作、2026 年才真正成熟——这个时间线看起来很长,但从技术演进的角度来说,十年时间完成从理念到成熟的闭环,其实已经算很快了。
所以不管是选技术、选工具,还是做技术决策,时机和场景的判断比技术本身的优劣更重要。Web Components 在 2015 年是个糟糕的选项,在 2026 年却是个明智的选择——技术没变,变化的是时机和生态。这个道理其实适用于很多技术领域。
希望你在读完这篇文章后,不只是知道了 Web Components 怎么用,更能理解它适合什么场景、不适合什么场景,以及如何判断你的团队是否适合采用它。毕竟,做出正确的技术选择,远比熟练使用某个技术更重要。
我在过去几年中亲眼见证了 Web Components 从「被嘲笑」到「被认真对待」再到「被大规模采用」的完整过程。刚开始用 Web Components 的时候,很多人不理解为什么要用这样一个「非主流」的技术。但当我们搭建出第一个跨框架组件库、让三个不同技术栈的团队都用上了同一套按钮和输入框时,质疑的声音就消失了。事实上,技术选型中很多时候不是谁对谁错的问题,而是在正确的时间做出正确的选择的问题。Web Components 在 2026 年终于等到了它的正确时间。
对于还在犹豫要不要尝试 Web Components 的团队,我的建议是:先在一个小范围内试点。用一个非核心的、低风险的组件试试水,比如一个提示框或者一个加载动画。如果在试点过程中发现不行,代价很小;如果发现效果很好,就可以逐步推广。这种渐进式的采用方式远比「一上来就把整个 Design System 用 Web Components 重写」要稳妥得多。