Web Components 我是从设计系统跨框架复用的场景里,才慢慢摸清它和框架组件的差别。
Q1: Web Components 的历史?为什么要有它?
1
框架组件的碎片化困境 (2010 年代初)
在 React(2013)、Angular(2016 重写)、Vue(2014)等框架各自推出自己的组件模型之后,前端社区面临一个尴尬的问题:每个框架的组件都是私有实现——React 组件只能在 React 生态中使用,Vue 组件只能在 Vue 生态中使用。如果你是一个 UI 库的作者,你要么只支持一个框架,要么为每个框架各写一套。这种碎片化让”跨框架共享组件”成了一个不可能的任务。
2
W3C 的标准化尝试 (2011-2018)
2011 年,W3C 开始制定 Web Components 规范,目标是让浏览器原生支持组件化——不依赖任何框架,用 HTML、CSS、JavaScript 就能创建可复用的、封装良好的自定义元素。规范包含三大核心技术:Custom Elements(自定义元素)、Shadow DOM(影子 DOM)、HTML Templates(模板标签)。2018 年,Custom Elements v1 和 Shadow DOM v1 在主流浏览器中全面落地。
3
现实的落差:框架仍占主导 (2018-2022)
尽管 Web Components 规范已经成熟,但它在实际项目中的采用率远低于预期。原因包括:(1) 开发体验远不如 React/Vue 流畅——没有声明式的模板语法、没有响应式系统、没有虚拟 DOM 的 diff 算法;(2) Shadow DOM 的样式隔离太”绝对”——外部 CSS 无法穿透,导致主题定制困难;(3) SSR 支持困难——Shadow DOM 的隔离特性使得服务端渲染变得复杂。Web Components 逐渐被定位为”底层基础设施”,而不是”日常开发工具”。
4
设计系统中的复兴 (2022 至今)
近年来,Web Components 在设计系统和跨框架共享组件场景中找到了真正的价值。Microsoft 的 Fluent UI、Adobe 的 Spectrum、Salesforce 的 Lightning Web Components 等大型设计系统都基于 Web Components 构建。原因很简单:一个设计系统需要同时服务于 React、Vue、Angular 等多个技术栈的团队——只有 Web Components 这种”平台级”方案才能做到框架无关。此外,Lit(Google 推出的 Web Components 库)大幅改善了开发体验,成为了 Web Components 生态的事实标准。
Web Components 是浏览器原生的组件化标准,它用 Custom Elements + Shadow DOM + HTML Templates 实现了框架无关的组件封装,是设计系统和跨框架场景的最佳选择。
🚫 框架私有组件
React 组件只能在 React 生态中使用。如果你是一个 UI 库作者,要支持 React + Vue + Angular 三个技术栈,你需要维护三套完全不同的组件实现。每修复一个 bug,要改三份代码。每新增一个组件,要写三遍。
✅ Web Components 原生组件
一个
<my-button> 自定义元素,浏览器原生支持。在 React 中当 <my-button> 使用,在 Vue 中也当 <my-button> 使用,在纯 HTML 中还是当 <my-button> 使用。一次编写,到处使用。设计系统的核心组件只需要维护一份代码。Q2: Web Components 核心技术(Custom Elements、Shadow DOM、HTML Templates)
Web Components 的三大核心技术各司其职,缺一不可:Custom Elements(自定义元素)
Shadow DOM(影子 DOM)
HTML Templates(HTML 模板)
Q3: Web Components vs React/Vue 组件
基础梳理
专家视角深度剖析
很多人只能背下上面的表格,但实际更在意的是:Web Components 和框架组件的根本设计哲学差异是什么?它们能共存吗? 答案在于**“平台级组件” vs “框架级组件”的定位差异**。Web Components 是浏览器平台提供的底层组件原语——它只负责三件事:自定义标签名、DOM 封装、样式隔离。它不提供声明式模板、响应式数据绑定、虚拟 DOM diff 等高级能力。这些能力是框架层的职责。因此,Web Components 和框架组件不是”替代”关系,而是”互补”关系——Web Components 提供跨框架的组件基座,框架提供开发体验和性能优化。Q4: Web Components 的实际应用
设计系统中的应用
微前端中的应用
渐进式增强原生 HTML
十、及格回答 vs 满分回答
问题:“Web Components 和 React 组件有什么区别?你在什么场景下会选择 Web Components?”
及格回答(60-70分):“Web Components 是浏览器原生的组件化方案,用 Custom Elements 和 Shadow DOM 实现。React 组件是 React 框架的组件。Web Components 的好处是不用依赖框架,但开发体验不如 React 好。”问题出在:知道概念但不理解定位和适用场景。 满分回答(90-100分):
Web Components 和 React 组件的根本差异在于**“平台级原语” vs “框架级抽象”的定位。Web Components 是 W3C 标准化的浏览器原生能力,它只负责三件事:自定义标签名(Custom Elements)、DOM 封装(Shadow DOM)、模板复用(HTML Templates)。它不提供**声明式模板、响应式数据绑定、虚拟 DOM diff 等高级能力——这些是框架层的职责。[1]
因此两者不是”替代”关系,而是”互补”关系。Web Components 的最大价值在于跨框架共享组件。当你构建一个需要同时服务于 React、Vue、Angular 等多个技术栈团队的设计系统时,Web Components 是唯一可行的方案——用 Lit 编写组件,打包为标准的 Custom Elements,任何框架都能直接使用 <ds-button variant="primary">Click</ds-button> 这样的原生标签。Adobe 的 Spectrum、Microsoft 的 Fluent UI、Salesforce 的 Lightning Web Components 都是这种架构。[2]
但 Web Components 不适合替代框架组件做日常业务开发。原因有三:(1) 没有响应式系统,状态变化需要手动调用render(),开发效率远低于 React/Vue;(2) Shadow DOM 的样式隔离太”绝对”,外部 CSS 无法穿透,导致主题定制困难(需要通过 CSS 自定义属性::part()来间接控制);(3) SSR 支持困难——Shadow DOM 的封装特性使得服务端渲染需要额外的序列化步骤。在实际项目中,我的选型策略是:业务组件用框架原生方案(React 用函数组件 + Hooks,Vue 用 SFC),跨框架共享的基础组件用 Web Components + Lit。
常见追问
Shadow DOM 的 mode: 'open' 和 'closed' 有什么区别?
Shadow DOM 的 mode: 'open' 和 'closed' 有什么区别?
答题要点:
解释 Shadow Root 的可访问性差异。高分回答:
“使用建议:大多数场景使用
mode: 'open' 表示外部 JS 可以通过 element.shadowRoot 访问 Shadow Root 对象,直接操作内部 DOM。mode: 'closed' 表示 element.shadowRoot 返回 null,外部 JS 无法访问内部 DOM。mode: 'open'(方便调试和测试)。只有在需要严格防止外部篡改内部结构的安全敏感场景中,才使用 mode: 'closed'。注意:closed 模式并非真正的安全屏障——有经验的开发者可以通过 monkey-patch attachShadow 来绕过限制。”如何从外部样式化 Shadow DOM 内部的元素?
如何从外部样式化 Shadow DOM 内部的元素?
答题要点:
列举所有合法的样式穿透方式。高分回答:
“有四种合法的方式从外部影响 Shadow DOM 内部的样式:
1. CSS 自定义属性(继承穿透):CSS 自定义属性可以穿透 Shadow DOM 边界继承进去。2. ::part() 伪元素:Shadow DOM 可以通过 3. CSS 继承属性:
part 属性选择性地暴露特定元素给外部样式化。color、font-family、font-size 等继承属性会自动穿透 Shadow DOM 边界。
4. ::slotted() 伪元素:Shadow DOM 内部可以通过 ::slotted() 样式化被投射进来的 slot 内容。Custom Elements 的生命周期回调有哪些?执行顺序是什么?
Custom Elements 的生命周期回调有哪些?执行顺序是什么?
答题要点:
完整列举四个生命周期回调并解释执行顺序。高分回答:
“Custom Elements 有四个生命周期回调:
1.
constructor():元素实例被创建时调用。用于初始化状态、创建 Shadow DOM、绑定事件。注意:不要在此时访问属性或子元素(它们还未就绪)。
2. connectedCallback():元素被插入 DOM 树时调用。用于渲染内容、获取属性值、添加事件监听。这是最常用的生命周期回调。
3. disconnectedCallback():元素被从 DOM 树移除时调用。用于清理事件监听、取消定时器、释放资源。
4. attributeChangedCallback(name, oldVal, newVal):被监听的属性值变化时调用。需要配合 static get observedAttributes() 使用。
执行顺序(以 <my-button variant="primary">Click</my-button> 为例):constructor() → attributeChangedCallback('variant', null, 'primary') → connectedCallback()。移除时:disconnectedCallback()。”Web Components 如何实现响应式数据绑定?
Web Components 如何实现响应式数据绑定?
答题要点:
对比原生实现和 Lit 框架的实现方式。高分回答:
“原生 Web Components 没有内置的响应式系统。你需要手动实现属性变化 → 重新渲染的逻辑:这种手动方式效率低且容易出错。Lit 框架通过响应式属性系统大幅简化了这个过程:Lit 内部使用了
Proxy 和脏检查机制来追踪属性变化,自动触发组件重新渲染(仅更新变化的 DOM 部分,类似虚拟 DOM 的 diff,但直接操作真实 DOM)。这就是为什么 Lit 的开发体验接近 React/Vue,但性能更好(无虚拟 DOM 的开销)。”Web Components 如何处理表单提交?
Web Components 如何处理表单提交?
答题要点:
解释 ElementInternals API 和表单关联机制。高分回答:
“HTML 原生表单元素(如 现在
<input>、<select>)在 <form> 提交时会自动将值包含在表单数据中。但自定义元素默认不会。要让 Web Components 参与表单提交,需要使用 ElementInternals API:<form><my-input name="username"></my-input></form> 提交时,username 的值会被自动包含在 FormData 中。ElementInternals 还支持表单验证(setValidity)和可访问性(ariaLabel)。”Lit 框架的核心原理是什么?为什么比原生 Web Components 好用?
Lit 框架的核心原理是什么?为什么比原生 Web Components 好用?
答题要点:
解释 Lit 的响应式属性、模板标签和高效更新机制。高分回答:
“Lit 是 Google 推出的 Web Components 库,它的核心改进有三点:
1. 响应式属性系统:通过
static properties 声明组件属性,Lit 自动追踪属性变化并触发重新渲染。开发者不需要手动在 attributeChangedCallback 中调用 render()。
2. 高效的模板更新:Lit 使用带标记的模板字面量(tagged template literals)实现了一种轻量级的 DOM diff 机制。html\<span>{this.count}</span>\`` 会在首次渲染时创建 DOM 模板,后续更新只替换变化的部分(`的文本节点),而不是重新创建整个 DOM 树。这比 React 的虚拟 DOM diff 更高效——因为 Lit 直接操作真实 DOM,没有虚拟 DOM 的序列化/反序列化开销。 **3. 极小的体积**:Lit 的核心库只有约 5KB (gzip),远小于 React 的 40KB。对于嵌入式场景(如第三方 widget)非常友好。 **底层实现**:Lit 的模板系统基于 HTML 的<template>标签和cloneNode API,首次渲染时创建模板克隆,后续更新通过 marker 注释节点(<!—lit-part—>`)定位需要更新的部分。”Web Components 在微前端架构中有什么作用?
Web Components 在微前端架构中有什么作用?
答题要点:
结合微前端的跨技术栈共享需求解释。高分回答:
“Web Components 在微前端架构中有三个核心价值:
1. 跨技术栈共享 UI 组件:在主应用中注册一套全局的 Web Components(如
<global-modal>、<global-toast>),无论子应用用 React、Vue 还是 Angular,都能直接使用这些组件,无需各自实现。
2. 样式隔离的天然方案:Shadow DOM 提供了比 CSS Modules 或 Scoped CSS 更强的样式隔离——子应用的样式不会泄漏到主应用,主应用的全局样式也不会影响子应用。这解决了微前端中最头疼的样式冲突问题。
3. 渐进式迁移的桥梁:当团队从 Vue 2 迁移到 Vue 3(或从 React Class 组件迁移到 Hooks)时,可以将公共组件先用 Web Components 重写,新旧技术栈都能使用,迁移完成后再决定是否保留 Web Components 版本。
注意事项:Web Components 的事件系统是原生 DOM 事件,不走 React 的合成事件系统。在 React 子应用中监听自定义事件需要使用 addEventListener 而不是 React 的 onClick。”Web Components 能否替代 React/Vue 做日常业务开发?
Web Components 能否替代 React/Vue 做日常业务开发?
答题要点:
客观分析限制因素,给出明确的适用场景判断。高分回答:
“Web Components 不能替代 React/Vue 做日常业务开发,原因有四:
1. 缺乏响应式系统:原生 Web Components 没有
useState、reactive 等响应式原语,状态变化需要手动调用 render() 或依赖 Lit 等辅助库。
2. 模板表达力不足:没有 JSX 的表达力,没有 Vue template 的指令系统(v-if、v-for)。复杂的条件渲染和列表渲染需要手写 JavaScript 逻辑。
3. 生态系统薄弱:没有像 React Router、Redux、Vue Router、Pinia 这样的成熟生态。路由、状态管理、表单验证等基础设施需要自行搭建或选择小众方案。
4. SSR 困难:Shadow DOM 的封装特性使得服务端渲染需要额外的序列化步骤,没有 Next.js/Nuxt.js 这样的成熟 SSR 框架支持。
适用场景:设计系统的基础组件(按钮、输入框、弹窗等低交互组件)、跨框架共享的工具组件、嵌入式 widget(第三方 SDK 的 UI 部分)。”📚 延伸阅读与引用参考
- 🔗 MDN - Web Components:MDN 对 Web Components 三大技术的完整教程和 API 参考。[1]
- 📖 Lit 官方文档:Lit 框架的完整文档,包含组件开发、样式系统、路由等最佳实践。[2]
- 🟢 Custom Elements v1 规范 - W3C:W3C 对 Custom Elements v1 的权威规范定义。[3]
