前端知库

Web Components 和框架组件有什么区别?

Web Components 是浏览器原生的组件标准,用 Custom Elements 定义标签、Shadow DOM 隔离样式,任何框架和纯 HTML 都能使用;但它只提供底层能力,没有声明式模板、响应式状态和高效的服务端渲染。React、Vue 组件只在各自框架里运行,却自带这些能力。适合把 Web Components 用在跨框架共享的设计系统和嵌入式小部件,应用内部仍用框架组件。

参考答案

简答

公司有 React 写的官网、Vue 写的后台,还有一个 jQuery 老系统,三边都要用同一套按钮和弹窗。做成 React 组件,Vue 和老系统用不了;做成 Web Components,也就是注册一个 <ui-button> 这样的原生标签,三边都能直接写进 HTML。这是两者最根本的区别:Web Components 是浏览器标准,框架组件只在自己的框架里运行。

可浏览器只给了底层能力:定义标签和生命周期、用 Shadow DOM 隔离样式、用 slot 放子内容。框架组件有的声明式模板、响应式状态、组件间的逻辑复用、高效的服务端渲染,Web Components 都没有,状态变了得自己去改 DOM。所以 Vue 官方把两者看作互补的技术,而不是替代关系。

两者的数据传递方式也不一样。HTML 属性只能是字符串,复杂数据要通过 DOM 属性传;组件向外通知靠派发 CustomEvent,而不是传回调函数。React 19、Vue 3 都已经能正确处理这两件事。

要注意的是,原生 slot 是立即求值的,没法实现 Vue 作用域插槽那样的组合方式;样式随 Shadow DOM 一起打包,SSR 时会重复输出。所以常见做法是,跨框架共享的基础组件用 Web Components,应用内部的业务组件仍用框架组件。

能力上差在哪里

Custom Elements、Shadow DOM、template 各自做什么,以及怎样写一个完整的按钮组件,见《Custom Elements、Shadow DOM 和 template 分别负责什么?》。这里只比较它和框架组件。

Vue 官方文档“Web Components vs. Vue Components”一节讲得很清楚:两者都能定义可复用组件、传数据、发事件、管理生命周期,功能有重叠;但 Web Components 的 API 比较底层,真正做应用还需要平台没有提供的几样东西:

  • 声明式、高效的模板系统;
  • 响应式状态管理,方便在组件间抽取和复用逻辑;
  • 高效的服务端渲染和客户端 hydration。

文档还解释了 SSR 这一点:原生 Custom Elements 做 SSR,通常要在 Node.js 里模拟 DOM 再把改过的 DOM 序列化出来;而 Vue SSR 尽量编译成字符串拼接,效率高得多。

当然也可以自己在 Custom Elements 之上把这些补齐,Vue 文档的看法是,那就等于长期维护一个自研框架。所以写 Web Components 时,通常会借助 Lit 这类库,或者用 Vue 的 defineCustomElement 把 Vue 组件包装成原生标签。

方面Web ComponentsReact / Vue 组件
运行范围任何框架、纯 HTML只在对应框架里
模板自己操作 DOM,或用 template 克隆JSX、模板语法,声明式
状态更新自己监听属性、手动更新 DOM响应式或重新渲染,框架负责更新 DOM
样式隔离Shadow DOM 原生隔离CSS Modules、scoped CSS 等约定
向外通知dispatchEvent(new CustomEvent(...))回调 props、emit
组合原生 slot,立即求值children、作用域插槽、render props
SSR需要声明式 Shadow DOM 等方案框架内置

数据和事件怎样跨过边界

HTML 属性(attribute)只能是字符串,写在标签上;DOM 属性(property)是元素对象上的字段,可以是任意 JavaScript 值。框架往自定义元素上传数据时,必须决定用哪一种。

React 文档的说明是:默认情况下,JSX 里绑定的值作为 attribute 传入,非字符串会被序列化,value={[1,2,3]} 会变成字符串 "1,2,3";但如果这个属性名在元素构造时就出现在类实例上,React 会把它当作 property,直接传入原始值。事件方面,自定义元素通常派发 CustomEvent,在 JSX 里可以用 on 前缀监听。

Vue 3 的做法类似:给自定义元素设置 prop 时,用 in 运算符检查元素上有没有这个 DOM 属性,有就优先当作 property 设置;遇到元素没有正确定义属性的少数情况,可以用 .prop 修饰符强制。另外 Vue 默认会先把不认识的标签当作 Vue 组件去解析,要用 compilerOptions.isCustomElement 告诉它哪些是自定义元素,否则开发环境会报“无法解析组件”的警告。

事件要传出 Shadow DOM,也有一个细节。MDN 说明,浏览器派发的 click 这类 UI 事件都是 composed 的,能跨过 Shadow DOM 边界;可自己 new CustomEvent() 时默认不是,要同时设置 bubbles: true 和 composed: true,外面的框架才能在宿主元素上监听到。

有哪些限制是框架组件没有的

Vue 文档专门列了两点。第一,原生 slot 是立即求值的:插进去的内容不管组件要不要显示,都已经创建好了,接收方没法控制什么时候、是否渲染它,也就做不出 Vue 作用域插槽这种“组件把数据交给插槽内容来渲染”的组合方式。

第二,带 Shadow DOM 样式的自定义元素,目前要把 CSS 嵌在 JavaScript 里,在运行时注入 shadow root;SSR 时每个实例的样式还会在 HTML 里重复出现。Vue 单文件组件则可以把 scoped 样式提取成普通 CSS 文件。

什么时候用哪个

适合用 Web Components 的场景,是“使用方的技术栈不受你控制”:跨多个框架共享的设计系统基础组件,要嵌入第三方网站的小部件,比如客服按钮、支付组件,以及微前端里需要隔离样式的边界。组件库怎样设计才稳定,见《前端组件库怎样设计才能让业务方稳定使用?》。

应用内部的页面和业务组件,状态多、组合复杂、要 SSR,用框架组件更省事。两者可以共存:React 或 Vue 应用里直接使用 Web Components 写的基础组件。

常见追问

Web Components 能做到“一次编写,永不过时”吗?

Vue 文档认为这个看法过于简单。浏览器标准本身确实稳定,但要做成应用,就得在它上面补模板、状态和 SSR,不管是自己写还是用基于 Custom Elements 的框架,都要接受这些方案自己的演进和变化。

Shadow DOM 里的样式完全改不了吗?

不是。继承属性(比如 color、font)和 CSS 自定义属性会穿透进去,组件作者还可以用 part 属性暴露内部元素,让外部用 ::part() 修改。

React 以前为什么不太适合用 Web Components?

React 19 的发布博客解释过:以前的版本把不认识的 props 当作 attribute 而不是 property,所以复杂数据要用 ref 手动设置。React 19 起完整支持自定义元素,客户端渲染时,和元素实例上属性同名的 props 按 property 赋值,其余按 attribute;服务端渲染时,字符串、数字、true 这类原始值输出成 attribute,对象、函数和 false 则省略。