一、为什么两种架构走向了不同的道路1.1 UI 的本质是什么要理解 React Fiber 和 Vue 响应式系统为何走向截然不同的架构路径我们必须回到一个更根本的问题用户界面的本质是什么。React 团队给出的答案是——UI 是状态的函数UI f(state)。这个看似简单的等式蕴含着深刻的架构决策如果 UI 只是状态的纯粹映射那么每次状态变化时整个 UI 都应该被重新计算框架的职责是通过 diff 算法来最小化实际的 DOM 操作。React 的虚拟 DOM 和 Fiber 架构都是这一观点的工程实现它们假设状态变化是不可预测的、细粒度的因此需要一个通用的运行时调度系统来处理任意复杂度的更新。这种设计赋予了 React 极强的灵活性和表达能力但也带来了不可避免的运行时开销——每一次更新都需要走过渲染 - 虚拟 DOM 树构建 - Diff - Patch的完整链路。Vue 的创始人尤雨溪对这个问题给出了不同的回答。在他看来UI 的本质是响应式数据与 DOM 之间的绑定关系。当开发者声明了一个模板Template其中的每一个插值表达式{{ }}、每一个指令v-bind、v-if、v-for都是在建立数据到视图的明确映射。Vue 的核心在于这种映射关系在编译时就可以被静态分析出来。因此Vue 选择将大部分优化工作前置到编译阶段通过编译器生成带有优化标记的渲染函数让运行时的更新工作变得精准而高效。Vue 3 的 Proxy 响应式系统进一步强化了这种理念——当数据变化时框架精确知道哪些组件、哪些 DOM 节点需要更新不需要进行全树扫描。两种框架的分歧从这一刻起就已经注定React 押注运行时调度的通用性和灵活性Vue 押注编译时优化的精准性和效率。1.2 两条路径的技术DNAReact 的技术 DNA 可以追溯到底层系统编程的启发。Fiber 架构的设计者 Andrew Clark 曾明确表示Fiber 是对操作系统线程调度模型的借鉴。在操作系统中进程调度器需要在多个任务之间分配 CPU 时间片确保高优先级任务如用户输入能够及时响应同时不让低优先级任务如后台计算饿死。React Fiber 将同样的思想引入了 JavaScript 的单线程环境通过将渲染工作拆分为可中断的工作单元并利用浏览器的requestIdleCallback机制React 可以在每一帧的空闲时间内执行一小部分渲染工作高优先级更新则可以随时 抢占 当前工作。这种架构赋予了 React时间切片和并发渲染的能力使得 React 能够在不阻塞主线程的前提下处理大规模组件树的更新。Vue 的技术 DNA 则源于数据绑定和依赖追踪。Vue 2 使用Object.defineProperty对数据对象进行递归劫持在 getter 中收集依赖在 setter 中触发更新。Vue 3 则将这一机制升级为基于 ES6 Proxy 的响应式系统配合ReflectAPI 实现更完整、更高效的拦截。Vue 的核心设计哲学是让框架自动追踪数据与视图之间的依赖关系开发者无需手动声明依赖不像 React 的useEffect需要显式传递依赖数组。当ref或reactive对象的值发生变化时Vue 的响应式系统能够精确通知到依赖于该数据的每一个副作用Effect包括组件的重新渲染、computed属性的重新计算、watch回调的执行等。这种自动追踪、精确触发的机制使得 Vue 在大多数场景下能够实现O(1) 的更新复杂度——即更新成本与受影响的节点数量成正比而非与组件树的总规模成正比。1.3 核心差异一览维度React FiberVue 响应式系统核心差异UI f(state)通用运行时调度数据驱动视图编译时优化 响应式追踪更新粒度组件级别需要 diff 确定实际变更属性级别精确追踪依赖调度模型协作式多任务Cooperative Scheduling依赖触发式Dependency-driven可中断性原生支持Time Slicing需配合nextTick批量处理编译角色次要JSX 转译核心模板编译 优化标记生成内存模型双缓冲Current / WorkInProgress 两棵树代理对象 Effect 依赖图学习曲线中等需理解 hooks 规则、闭包陷阱平缓模板语法直观回到顶部二、React Fiber在单线程世界里的调度器2.1 Stack Reconciler 的困局在 React 16 之前的 Stack Reconciler 时代React 的更新过程可以简单概括为一撸到底。当组件状态发生变化时React 会从根节点开始递归遍历整棵组件树计算新的虚拟 DOM 树与旧的树进行 Diff最后一次性将所有变更提交到真实 DOM。这个过程完全同步且不可中断——一旦开始就必须等到全部完成才能将控制权交还给浏览器。对于小型应用这种方式工作得很好因为整个更新过程可能只需要几毫秒。但随着应用规模的增长组件树可能包含数千个节点一次完整的 reconciliation 可能消耗数十甚至上百毫秒直接阻塞浏览器的主线程。这种阻塞带来的用户体验问题是灾难性的。我们想象一下用户在搜索框中输入文字同时后台正在接收实时数据更新。在 Stack Reconciler 中数据更新触发的重渲染可能会完全占用主线程 100ms在这段时间内用户的键盘输入事件被挂在事件队列中无法得到响应——用户会感觉卡顿。更严重的是动画在这一期间完全停滞因为浏览器没有机会执行requestAnimationFrame回调。React 团队意识到问题的根源不在于虚拟 DOM 本身而在于 JavaScript 的执行模型——调用栈是 后进先出的、不可抢占的数据结构一旦进入深层递归就没有优雅的方式来暂停当前工作去处理更紧急的任务。2.2 Fiber 的创新重新实现调用栈React Fiber 的创新点在于它对这一底层问题的回应如果浏览器的调用栈不够灵活那就自己实现一个。Fiber 架构的本质是一种用户空间调度器它将原本由 JS 引擎管理的调用栈转换为显式维护的链表数据结构。每一个 React 组件实例不再只是一个函数调用而是一个持久化的 Fiber 节点对象其中包含了child第一个子节点、sibling下一个兄弟节点和return父节点三个指针构成了一棵可任意遍历、暂停和恢复的树形链表。这种数据结构的选择绝非偶然。链表结构使得 React 可以彻底放弃递归recursion改用循环loop来遍历组件树。在循环的每一次迭代中React 处理一个 Fiber 节点然后检查当前帧的剩余时间。如果剩余时间不足React 默认设置了一个约5ms 的帧预算或者检测到有更高优先级的更新到来React 可以立即保存当前的工作进度记录下一个待处理的 Fiber 节点引用将控制权交还给浏览器然后在下一帧的requestIdleCallback回调中无缝恢复工作。Andrew Clark 将 Fiber 描述为一个专门用于 React 组件的虚拟栈帧——它的核心优势在于这些栈帧存储在堆内存中React 可以完全控制它们的执行顺序和时机这是操作系统调用栈所不具备的能力。2.3 双缓冲架构与两阶段提交Fiber 架构引入了双缓冲的内存模型这是另一个深刻影响 React 更新行为的创新。React 在内存中同时维护两棵 Fiber 树一棵是current树代表了当前屏幕上真实 UI 的状态另一棵是workInProgress树用于进行正在进行的渲染计算。当更新触发时React 并不会直接修改current树而是基于它克隆出一棵workInProgress树所有的 reconciliation 工作都在这棵草稿树上进行。这个设计的精妙之处在于渲染过程完全不会影响用户看到的界面——即使渲染过程中途被中断或完全丢弃用户看到的依然是current树对应的一致 UI。当workInProgress树的所有工作完成后React 进入提交阶段Commit Phase。这是一个同步、不可中断的阶段React 将workInProgress树的所有副作用DOM 插入、更新、删除以及生命周期函数和useEffect回调的调度一次性应用到真实 DOM 上然后原子性地将workInProgress树切换为新的current树。两阶段架构的严格分离是 React 并发特性的基石渲染阶段Render Phase可以被打断和重启因为它只操作内存中的workInProgress树提交阶段Commit Phase必须是原子的因为此时正在修改用户可见的界面任何不一致都会导致视觉闪烁。是否状态更新触发是否有更高优先级任务?保存当前进度yield 控制权处理高优先级任务恢复之前工作Render Phase构建 workInProgress 树生成 Effect ListCommit Phase同步提交 DOM 变更切换 current 指针调度 useEffect2.4 优先级调度与 Lane 模型React 18 进一步演化出了Lane 优先级模型用 31 位的二进制数来表示不同类型的更新优先级。每一位代表一个通道Lane不同的交互类型用户输入、点击、数据加载、过渡动画等被分配到不同的 Lane 上。React 可以精确判断哪些更新更紧急并支持 Lane 的纠缠entanglement机制——当高优先级更新和低优先级更新之间存在数据依赖时React 会自动将它们合并渲染防止出现视觉不一致。这种精细的优先级控制系统使得 React 能够在极端复杂的并发场景中依然保持用户交互的流畅性但也显著增加了框架的运行时复杂度和学习成本。回到顶部三、Vue 响应式系统让数据自己告诉你它变了3.1 从 defineProperty 到 Proxy响应式技术的进化Vue 的响应式系统经历了两代重大演进。Vue 2 使用Object.defineProperty为对象的每一个属性定义 getter 和 setter在属性被读取时收集依赖在被修改时触发更新。这个方案在当时是创新的但它有几个根本性缺陷首先Object.defineProperty只能拦截已经存在的属性无法检测对象的新增属性和数组索引的变化这也是 Vue 2 需要Vue.set和Vue.deleteAPI 的原因其次它需要对数据对象进行深度递归遍历在初始化时就为每一层嵌套对象的每一个属性都设置 getter/setter这在处理大型数据对象时会产生大的性能开销。Vue 3 的响应式系统基于 ES6 的Proxy对象进行了彻底重写。与Object.defineProperty不同Proxy可以拦截对目标对象的任何操作——包括属性读取、赋值、删除、枚举、函数调用、in运算符甚至new操作。这意味着 Vue 3 不再需要深度递归初始化代理是懒的只有当访问到某个嵌套对象时才会递归地为该对象创建代理。更重要的是Proxy让 Vue 3天然支持 Map、Set、WeakMap、WeakSet等 ES6 数据结构以及数组的所有操作包括直接通过索引赋值和修改length无需任何特殊处理。在 Vue 3 的源码中reactive()函数通过new Proxy(target, mutableHandlers)创建响应式对象其中mutableHandlers包含了get和set拦截器。get拦截器使用Reflect.get(target, key, receiver)读取属性值ReflectAPI 的设计目的正是为了与Proxy配合使用提供更完整和规范的元编程能力同时调用track()函数进行依赖收集set拦截器使用Reflect.set()写入新值然后调用trigger()函数通知所有依赖进行更新。这种 Proxy Reflect 的组合已经成为现代 JavaScript 元编程的标准范式。3.2 依赖收集的三剑客TargetMap、Dep、EffectVue 3 的响应式系统内部维护了一个精巧的全局依赖追踪结构。其核心是三个关键数据结构首先是targetMap一个WeakMapobject, Mapstring | symbol, SetReactiveEffect结构。它的作用是建立响应式对象 - 属性键 - 依赖集合的三层映射。WeakMap的选择非常重要——它允许垃圾回收器在响应式对象不再被引用时自动回收其对应的依赖信息防止内存泄漏。当track()被调用时Vue 会根据当前被访问的响应式对象和属性键找到或创建对应的依赖集合Dep然后将当前正在执行的ReactiveEffect实例添加到这个集合中。其次是ReactiveEffect类它是 Vue 响应式系统中副作用的抽象表示。组件的渲染函数、computed属性的计算函数、watch的回调函数本质上都是ReactiveEffect的不同实例。每个ReactiveEffect有一个run()方法用于执行副作用以及一个deps数组用于记录它依赖于哪些Dep集合。这种双向记录的机制——Effect 记录它依赖了哪些 DepDep 记录哪些 Effect 依赖了它——是实现精确更新的关键。当响应式数据变化时trigger()函数只需要找到对应的Dep集合遍历其中的所有 Effect 并重新执行即可。最后是调度器Scheduler。Vue 并不会在数据变化时立即同步执行所有副作用而是将它们推入一个队列通过nextTick机制进行异步批量刷新。这意味着在同一个事件循环中发生的多个数据变化只会触发一次统一的 DOM 更新——这是 Vue 性能优化的重要手段。通过Promise.then或降级到setImmediate/setTimeoutVue 确保所有同步的数据变更都完成后才在下一个微任务中执行副作用这种批量处理策略大幅减少了不必要的重复渲染。读取属性对象属性添加触发获取 EffectsnextTick响应式对象 ProxytracktargetMapMap: key - DepSet of Effects当前 Effect修改属性trigger批量调度更新执行 Effect/DOM更新3.3 编译器的智慧从模板到优化标记Vue 的响应式系统之所以高效很大程度上归功于其编译器的静态分析能力。与 React 的 JSX 不同Vue 使用基于 HTML 的模板语法。这种看似限制性的设计实际上为编译器优化打开了巨大的空间。当 Vue 编译器分析一个模板时它能够识别出哪些部分是静态的不会随数据变化哪些是动态的绑定响应式数据。Vue 3 的编译器引入了多项革命性的优化技术静态提升Static Hoisting将静态节点从渲染函数中提取出来只在首次渲染时创建一次后续更新完全跳过这些节点Patch Flags为每一个动态节点打上一个优化标记精确指示该节点的哪个部分可能变化文本内容、类名、样式、属性等这样运行时的 diff 算法可以跳过完整的 props 比较只检查可能发生变化的特定部分树扁平化打破了传统的递归 diff 模式将所有动态节点收集到一个扁平数组中diff 时只需要遍历这个数组而非整棵树。这些编译时优化的综合效果使得 Vue 3 的虚拟 DOM 更新效率远超传统的全树 diff 实现——虽然 Vue 仍然使用虚拟 DOM但它已经是一个被编译器武装到牙齿的高度优化版虚拟 DOM。回到顶部四、业内其他框架百花齐放的方案4.1 Svelte编译器即框架如果说 React 代表了运行时最大化的极端那么 Svelte 则代表了编译时最大化的另一个极端。Svelte 的创造者 Rich Harris 提出了一个激进的问题如果框架在构建时就知道你的组件会如何变化那为什么还要在运行时做这些工作。Svelte 的核心架构决策是将框架本身编译掉——最终运行在浏览器中的代码几乎是纯粹的手写 JavaScript DOM 操作没有虚拟 DOM没有响应式运行时库没有 diff 算法。Svelte 5 进一步引入Runes如$state、$derived、$effect将响应式模型从隐式的编译器魔法转变为显式的信号Signals机制。编译器分析组件模板中的每一个响应式绑定生成精确的 DOM 更新代码。当$state的值变化时编译生成的代码直接调用textNode.data newValue或element.setAttribute(class, newClass)没有任何中间抽象层。这种架构的代价是 Svelte 需要一个功能强大的编译器来处理各种边界情况但它的回报也是巨大的Svelte 应用的运行时体积极其微小约 2-3 KB gzip更新性能接近原生 JavaScript内存占用也远低于虚拟 DOM 方案。4.2 SolidJS Signals 驱动的细粒度响应式SolidJS 的创造者 Ryan Carniato 将细粒度响应式Fine-grained Reactivity推向了一个极致。Solid 同样不使用虚拟 DOM但它与 Svelte 的编译器驱动方式有所不同Solid 保留了 JSX 语法其编译器将 JSX 转换为高效的 DOM 创建和更新指令而响应式追踪则在运行时通过Signals完成。Solid 的createSignal返回一个 getter/setter 对当在 JSX 或其他响应式上下文中读取 signal 时依赖关系被自动建立当 signal 值变化时只有直接依赖于该值的 DOM 节点会被更新。SolidJS 的一个关键设计特点是组件函数只执行一次。这与 React组件函数在每次渲染时都重新执行和 Vue渲染函数在每次更新时重新执行有着根本不同。在 Solid 中组件的 setup 代码只在挂载时运行一次后续所有的更新都通过信号系统精确到达对应的 DOM 节点无需重新执行组件函数。这种设计消除了重新渲染的概念从根本上避免了虚拟 DOM 方案中因组件重渲染而产生的计算开销。在 js-framework-benchmark 中SolidJS consistently 排名最靠前与原生 JavaScript 的性能差距极小这验证了细粒度响应式架构在性能上的巨大潜力。4.3 Angular从 Zone.js 到 Signals 的转变Angular 作为一个历史悠久的企业级框架其架构演进代表了另一个维度的思考。长期以来Angular 依赖Zone.js进行变化检测——Zone.js 通过猴子补丁monkey-patching浏览器的所有异步 APIsetTimeout、Promise、XHR、DOM 事件等在任何异步操作完成后自动触发 Angular 的全局变化检测。这种方案的优点是开发者完全不需要关心何时触发更新——任何异步操作后 Angular 都会自动检查所有组件是否需要更新缺点是性能极差因为即使是最微小的状态变化也可能导致整个组件树的脏检查Dirty Checking。Angular 16 开始引入Signalssignal()、computed()、effect()标志着 Angular 正在从 Zone.js 的全局脏检查模型向细粒度响应式模型迁移。Angular 的 Signals 设计与 SolidJS 类似但提供了更渐进式的迁移路径——开发者可以逐步将组件从 Zone.js 迁移到 Signals而无需重写整个应用。Angular 的转变印证了一个行业趋势细粒度响应式正在成为前端框架的共识方向即使是传统上采用完全不同架构的框架也在向这一方向靠拢。4.4 各框架架构对比框架渲染策略响应式模型运行时体积更新粒度编译角色React 19Virtual DOM FiberHooks 自动 memoization~45 KB组件级React Compiler (构建时 memo)Vue 3Compiler-optimized VDOMProxy Effect 追踪~34 KB属性级核心静态提升、Patch FlagsVue Vapor无 VDOM直接 DOMProxy Effect 追踪~10 KB属性级核心编译为 DOM 操作Svelte 5无 VDOM编译后代码Runes (Signals)~3 KB语句级核心编译器即框架SolidJS无 VDOM编译后代码Signals (createSignal)~7 KBDOM 节点级JSX 编译 运行时追踪Angular 19incremental DOMSignals (迁移中)~120 KB属性级AOT 编译 Signals回到顶部五、两种框架什么场景下使用不一定对5.1 性能特性的场景化分析React Fiber 的并发调度能力在大规模、高频率、高并发更新的场景下展现出独特优势。比如一个复杂的股票交易仪表盘页面上有数十个实时数据流价格、成交量、订单深度同时用户正在与图表交互缩放、平移、选择时间范围。React 的 Fiber 架构允许用户交互高优先级实时打断数据更新低优先级确保图表操作始终保持 60fps 的流畅度而价格数据在后台以较低优先级逐步更新。如果没有 Fiber 的调度能力大量数据更新可能导致用户交互出现明显卡顿。React Compiler原 React Forget进一步通过编译时自动插入 memoization 来减少不必要的重渲染让开发者不再需要手动管理useMemo和useCallback。Vue 的响应式系统在大多数常规应用场景下提供更优异的更新效率和开发体验。由于 Vue 精确追踪了每一个数据属性与视图之间的依赖关系更新成本天然地与变更的影响范围成正比而非与组件树的总规模成正比。这意味着在一个包含 1000 个组件的页面中如果只有底部一个计数器发生变化Vue 只需要更新那个计数器对应的 DOM 节点而 React在没有 Compiler 优化的情况下可能需要重新渲染整个受影响的组件子树然后进行 diff。Vue 3.6 的 Vapor Mode 进一步将这一优势推向极致对于使用 Composition API 的组件Vapor Mode 可以在编译时直接生成 DOM 操作代码完全跳过虚拟 DOM实现与 SolidJS 媲美的性能。5.2 开发者体验心智模型与学习曲线React 的编程模型更接近 JavaScript 的函数式编程范式。HooksuseState、useEffect、useMemo等的引入虽然解决了类组件的逻辑复用问题但也带来了新的心智负担hooks 的调用顺序必须严格一致不能在条件语句中调用依赖数组需要手动维护遗漏依赖会导致 bug闭包陷阱stale closure是新手最常遇到的问题之一。React 的灵活性是一把双刃剑——它允许你以几乎任何方式组织代码但也意味着团队需要建立严格的代码规范来保持一致性。React Compiler 的出现正在缓解这些问题通过编译时自动优化替代了大部分手动 memoization 的工作。Vue 的编程模型则更加约定优于配置Convention over Configuration。模板语法{{ }}、v-if、v-for、v-bind对前端开发者来说非常直观因为它们本质上就是增强的 HTML。Composition API 提供了与 React Hooks 类似的逻辑组合能力但没有了调用顺序的限制也没有了依赖数组——因为 Vue 的响应式系统自动追踪依赖开发者不需要手动声明。这种自动依赖追踪的设计极大地减少了与响应式相关的 bug。对于初学者来说Vue 的渐进式设计意味着可以从一个简单的 script 标签引入开始逐步学习到完整的单文件组件SFC、Composition API、状态管理Pinia和路由Vue Router每一步都有明确的指导路径。5.3 生态React 的生态系统无疑是前端领域最为庞大和成熟的。从状态管理Redux、Zustand、Jotai、Recoil到路由React Router、元框架Next.js、Remix、UI 组件库Material-UI、Ant Design、Chakra UI、表单处理React Hook Form、Formik、数据获取TanStack Query、SWRReact 生态几乎覆盖了前端开发的每一个细分领域。Next.js 的 App Router 和 React Server Components (RSC) 代表了 React 生态在服务端渲染和全栈开发方向上的最新探索。对于大型企业和团队来说React 生态的广度和深度意味着几乎任何需求都能找到成熟的解决方案招聘拥有 React 经验的开发者也相对容易。Vue 的生态系统虽然规模不及 React但其整合度更高、一致性更好。Vue 官方维护的核心生态库Vue Router、Pinia、Vite、VueUse、Nuxt.js在 API 设计和发布节奏上保持高度统一这大大降低了开发者在不同库之间切换的认知成本。Nuxt.js 作为 Vue 的官方元框架提供了开箱即用的服务端渲染、静态生成、API 路由、自动导入等全栈功能其开发者体验在很多方面优于 Next.js。回到顶部六、融合的未来架构趋同与各自进化6.1 从对立到融合的行业趋势一个值得思考的现象是React 和 Vue 虽然起源于完全不同的架构哲学但是在最近的两年里两条技术路线正在呈现出趋同。React 通过 React Compiler 在编译时自动完成原本需要手动进行的 memoization 优化实质上是在借用编译时优化的思路来弥补虚拟 DOM 的性能缺陷Vue 通过 Vapor Mode 探索无虚拟 DOM 的编译策略实质上是在向 Svelte/Solid 的细粒度响应式范式靠拢。两者都在向对方擅长的领域延伸——React 增强编译时能力Vue 增强运行时调度能力。这种趋同也不是偶然而是前端架构演进的必然结果。无论是虚拟 DOM 还是细粒度响应式最终目标都是在状态变化时高效地更新界面。虚拟 DOM 的方案通过通用运行时 diff解决问题优点是灵活性和可预测性缺点是运行时开销细粒度响应式的方案通过编译时/运行时精确追踪解决问题优点是极致的性能缺点是更强的编译依赖和对编程模式的约束。最优的架构必然是在两者之间找到平衡点——利用编译器做尽可能多的静态分析和优化同时保留运行时的调度能力来处理动态和不可预测的场景。6.2 React 的未来Compiler Server ComponentsReact 团队正在全力推进三个方向的进化React Compiler构建时自动 memoization、React Server Components服务端组件零客户端 bundle、以及Offscreen Rendering离屏渲染用于预加载和保留组件状态。React Compiler 的成熟将从根本上改变 React 的性能优化范式——开发者不再需要手动编写useMemo、useCallback和React.memo编译器会在构建时自动完成这些优化且粒度通常比手动优化更细。React Server Components 则代表了 React 对如何减少客户端 JavaScript 体积这一问题的回答将纯数据展示型组件放在服务端执行只将交互型组件发送到客户端。这种服务器优先的架构Server-first Architecture正在通过 Next.js 的 App Router 成为 React 生态的主流范式。6.3 Vue 的未来Vapor Mode Alien SignalsVue 的未来路线图同样清晰而且让人期待。Vapor Mode的目标是让 Vue 在保持现有 API 不变的前提下实现 Soli