文章

性能优化策略对比深度解析

对比Vue编译时优化(静态提升、Block Tree、patchFlags)与React运行时调度(Fiber、时间切片、优先级),揭示两种优化哲学的本质差异

性能优化策略对比深度解析

一句话概括

Vue 把性能优化的核心放在编译时——用模板的静态分析能力在构建阶段就把「谁能跳过」标记好;React 把优化的核心放在运行时——用 Fiber 的可中断调度让浏览器永远不觉得卡。一个追求「默认就快」,一个追求「永远不会慢」。

核心知识点

1. 静态提升(Static Hoisting):编译时把不变的节点提走

Vue 编译器在构建阶段扫描模板,把永远不变的节点提到渲染函数外面,re-render 时直接复用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 编译前:看起来只是普通 HTML -->
<template>
  <div>
    <h1 class="title">我的应用</h1>        <!-- 🟢 永远不变 → 提升 -->
    <p>{{ user.name }} 您好</p>             <!-- 🔴 动态 → 使用 patchFlag -->
    <footer>© 2026 My App</footer>          <!-- 🟢 永远不变 → 提升 -->
  </div>
</template>

<!-- 编译后的渲染函数概念 -->
<script>
// 静态节点只创建一次,re-render 时直接复用
const _hoisted_1 = createElementVNode('h1', { class: 'title' }, '我的应用', -1 /* HOISTED */)
const _hoisted_2 = createElementVNode('footer', null, '© 2026 My App', -1 /* HOISTED */)

export function render(_ctx) {
  return (openBlock(), createBlock('div', null, [
    _hoisted_1,       // 直接复用
    createElementVNode('p', null, _ctx.user.name, 1 /* TEXT only */),  // 只标记文字变化
    _hoisted_2,       // 直接复用
  ]))
}
</script>

为什么 React 做不了? JSX 是 JavaScript 表达式,编译器不知道 <p>{greeting}</p> 里的 greeting 是静态还是动态,只能在运行时求值。这就是「模板的静态性 = 编译器的确定性」。

2. Block Tree:Diff 时只看动态节点

Vue3 在编译时把模板切分成一棵 Block Tree,每个 Block 内部维护一个 dynamicChildren 数组,只包含该 Block 范围内真正会变化的节点:

1
2
3
4
5
6
7
// 10 个静态节点 + 2 个动态节点的页面
// 传统 Diff:需要遍历比较全部 12 个节点
// Block Tree:只在 dynamicChildren 里比较那两个动态节点
block.dynamicChildren = [
  { el: span, flag: TEXT, key: 0 },    // 文本变化
  { el: div,  flag: CLASS, key: 3 },   // class 变化
]

对于静态内容占比 >70% 的页面(后台管理、文章详情、文档站),Block Tree 能让 Diff 工作量直接砍掉大部分。React 虽然也有 React.memo 跳过子树,但那是开发者手动控制的,而且跳过的是整个组件,粒度不如 Block Tree 精确。

3. Fiber 可中断渲染:浏览器永远不会被你卡住

React Fiber 把「一次渲染」拆成数百个微小的 Fiber 单元,每执行一个单元就检查「我还有时间吗?」:

1
2
3
4
5
6
7
8
9
10
11
// React 工作循环的简化模型
function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
    shouldYield = deadline.timeRemaining() < 1  // 只剩不到 1ms 就让路
  }
  if (nextUnitOfWork) {
    requestIdleCallback(workLoop) // 下一帧再继续
  }
}

效果:渲染 10000 个列表项时,Vue 可能一帧内搞定(快但可能掉帧),React 分 5 帧做(每帧最多 5ms,留 11ms 给动画和用户输入)。哪个更好?低端设备上 React 的体验更丝滑,高性能设备上 Vue 的宏观看「完成时间」更短。

4. 优先级调度(Lane Model):用户点击永远比数据加载重要

React 把所有更新分配了不同的「车道」:

1
2
3
4
5
// React 的 Lane 优先级模型
const SyncLane           = 0b0001  // 同步 → onClick 中的 setState
const InputDiscreteLane  = 0b0010  // 离散输入 → 键盘输入
const DefaultLane        = 0b0100  // 默认 → 普通数据更新
const TransitionLane     = 0b1000  // 过渡 → startTransition 包裹的更新

当用户正在输入搜索关键词时,每次击键产生的高优先级 InputDiscreteLane 更新会中断正在进行的搜索结果渲染(TransitionLane),优先响应输入。Vue 没有这个能力——它的策略是「我渲染够快,所以不需要优先级」。

5. 手动优化 vs 自动优化:useMemo 是成本还是保障?

1
2
3
4
// React:你需要三类 memo 才能拼出完整的优化链
const filtered = useMemo(() => items.filter(...), [items, query])  // 缓存计算
const onClick  = useCallback((id) => { ... }, [])                   // 稳定引用
const Item     = React.memo(function Item({ data, onClick }) { ... }) // 跳过渲染
1
2
3
4
5
6
7
8
9
<!-- Vue:三样都不需要,编译器替你搞定了 -->
<template>
  <Item v-for="data in filteredItems" :key="data.id" :data="data" @click="onClick" />
</template>
<script setup>
const filteredItems = computed(() => items.value.filter(...))  // 自动缓存
const onClick = (id) => { ... }  // 无需 useCallback,引用天然稳定
// 子组件默认跳过不变 props 的渲染
</script>

React 19 的 Compiler 会改变这个局面——它在编译时自动插入 memoization。但在此之前,React 开发者需要花更多精力在「怎么写才不慢」上。

其实你每天都在用

  1. v-for 的 key:你以为 key 只是去除 warning?在 Block Tree 里,key 决定了 dynamicChildren 的匹配方式——稳定的 key 让 keyed fragment 能做最小移动而非全量替换。
  2. computed vs useMemo 的依赖追踪:Vue 的 computed 自动追踪,你不用写依赖数组也不会出错;React 的 useMemo 依赖数组写漏一个就是 stale closure bug。
  3. 搜索框的防抖:在 React 里防抖搜素会频繁触发组件渲染(即使用了 useMemo),在 Vue 里防抖期间只要 computed 的结果不变,DOM 就不会更新——因为 Vue 的更新粒度是「用到这个值的绑定节点」,不是「整个组件」。
  4. <Suspense>:React 的 Suspense 让你在异步加载时展示 fallback,本质是 Fiber 调度的一种应用——它能「暂停」子树渲染但不阻塞兄弟节点。Vue 的 <Suspense> 做不到这种粒度。
  5. CSS 过渡 vs Framer Motion 动效:Vue 的 <Transition> 在 DOM 层面精准控制 enter/leave 类名,性能开销极小;React 生态里 Framer Motion 需要在 JS 层做动画插值计算,动画量大时会挤占 Fiber 的时间预算。

常见误解(FAQ)

❌ 误区:「Vue 性能比 React 好」

这取决于场景。Vue 在静态内容多的页面(后台、文档站)上默认更快,因为 Block Tree 跳过了大量 Diff。但 React 在需要复杂调度的交互场景(协作编辑、实时数据大屏)上体验更好,因为 Fiber 保证了主线程不阻塞。比较框架性能时,一定要加「在什么场景下」。

❌ 误区:「React.memo 和 useMemo 加得越多越快」

这是经典的 over-optimization。React.memo 本身有浅比较开销,useMemo 有缓存管理开销。对简单组件加 React.memo 可能比较的开销比重新渲染还大。原则:「先测量,再优化」——React DevTools Profiler 告诉你哪里慢,再针对性处理。

❌ 误区:「Vue3 的 Vapor Mode 就是去掉虚拟 DOM」

不只如此。Vapor Mode 把模板直接编译为细粒度 DOM 操作指令,绕过了 vdom 的 createDiff + patch 环节。这意味着不仅是「没有虚拟 DOM」,而是整个运行时链路更短。相当于把「编译时优化」推到了极致——每一行模板都变成了精准的 DOM 操作。

❌ 误区:「React Compiler 出来以后 Vue 就没有编译优势了」

React Compiler 主要做 memoization 注入,Vue 编译器做的事远不止 memo——静态提升、Block Tree、patchFlags、事件缓存,这些是基于「模板是可分析的 AST」这个前提的。JSX 没有这个前提,Compiler 只能做 JS 函数层面的分析,优化空间天然更小。

一句话总结

Vue 追求「框架替你把路铺好」(编译时多做工作),React 追求「给你最好的刹车和方向盘」(运行时调度能力)——选哪个取决于你是想让框架帮你跑得快,还是想在复杂路况下自己精准操控。

本文由作者按照 CC BY 4.0 进行授权