Vue 3 vs React 19:框架还在卷,核心原理就这些

背了忘、忘了背?不用了。这篇文章不罗列 API,只讲一件事------数据变了,页面怎么跟上。看完你就能串起 Vue 和 React 各自的整条链路。


开篇:为什么需要理解原理?

前端框架迭代快,新特性层出不穷,但翻来覆去就一条主线:数据变了,页面怎么跟上。 每个框架都在用不同的路径解决同一个问题。

Vue 的做法是:编译阶段分析模板 → 响应式系统追踪数据变化 → 精准更新 DOM。

React 的做法是:组件函数重新执行 → 新旧虚拟 DOM 对比 → 批量更新 DOM。

两条路线各有取舍,理解了取舍,你就不再纠结哪个更好------而是知道什么时候该用什么。


第一章:Vue 3

1.1 版本现状

截至 2026 年 6 月,Vue 3 稳定版为 3.5.35 。从 3.0 到 3.5,refreactivecomputedwatch 这套 API 完全互通,不存在 Vue 2 升 Vue 3 那种大规模重写。

正在开发中的 Vue 3.6带来了两个重要变化:

  • Vapor Mode:把模板直接编译成 DOM 操作代码,跳过虚拟 DOM 的创建和 diff。
  • alien-signals:底层依赖追踪从 Map/Set 换成双向链表。数据变化时只做一个轻量的"标记为脏"(push),真正读数据时才重新计算值(pull),减少了不必要的重复计算。

尤雨溪在 2025 年 State of Vue.js 讨论中明确过:即便未来有 Vue 4,也只会极少量的破坏性变更,不会重演 Vue 2 到 3 的大规模迁移。团队策略是把大特性消化在 3.x 里。


1.2 核心原理:一条流水线,三步走

Vue 的运转可以概括为:编译 → 响应式 → 渲染。我们从一个最简单的计数器看起:

vue 复制代码
<script setup>
const count = ref(0)
</script>
<template>
  <button @click="count++">{{ count }}</button>
</template>

第一步:编译------模板变代码

.vue 文件里的 <template> 浏览器不认识,Vue 的编译器把它翻译成渲染函数(JavaScript 函数),执行这个函数就会生成一棵虚拟 DOM 树。

编译器做了两个关键优化:

静态提升------模板里永远不变的部分,只创建一次,之后复用:

js 复制代码
const _hoisted = createVNode("span", null, "静态标题")  // 编译时一次搞定

render() {
  return createVNode("div", null, [
    _hoisted,                                           // 每次 render 直接复用
    createVNode("span", null, dynamicText, 1 /* TEXT */)
  ])
}

Patch Flags------编译器分析出每个动态节点"哪部分会变",给节点打一个数字标记:

标记 含义 例子
1 (TEXT) 只有文本内容会变 {{ message }}
2 (CLASS) 只有 class 会变 :class="{ active }"
4 (STYLE) 只有 style 会变 :style="{ color }"
8 (PROPS) 只有属性值会变 :id="dynamicId"

运行时 diff 看到标记是 1(TEXT),就知道只需比对文本,不用碰 class、style、属性。编译阶段分析得越清楚,运行时干的活就越少。这是 Vue 性能的核心秘密。

第二步:响应式------数据哪里变了,它就通知谁

Vue 3 用 ES6 的 Proxy 做响应式。你大概理解成这样就行:

js 复制代码
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key) // "有人读我了,记下来"
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key) // "值变了,通知所有读过我的人重跑"
      return true
    },
  })
}

const count = ref(0) 有个细节:0 是原始值(数字),不是对象。Proxy 只能代理对象,不能代理一个数字。所以 Vue 把 0 装进一个 { value: 0 } 的小盒子,再对这个盒子套一层 Proxy。读 count.value 就是打开盒子------触发 get,通知 Vue"有人在用这个数据";写 count.value = 1 就是往盒子里放新东西------触发 set,通知所有依赖者重跑。

如果用 reactive({ count: 0 }),那直接 state.count 就行,不需要 .value,因为 reactive 接收的本来就是对象。

为什么推荐 ref 而不是 reactive reactive 有一个陷阱:解构就丢响应式。

js 复制代码
const state = reactive({ name: "Lee" })
const { name } = state // name 现在是字符串 'Lee',跟 state 的 Proxy 断开关系了

name 不会触发更新。ref 不存在这个问题,因为 .value 始终指向那个盒子。

Vue 3.5+ 正式引入了 Reactive Props Destructure 特性。编译器会自动处理 defineProps 的解构逻辑,确保解构后的值依然是响应式的。

shallowRef 干嘛的? 有时候数据很大(比如上万条数据的列表),你不希望 Vue 递归地把每个字段都变成 Proxy------太慢了。shallowRef 只追踪 .value 的替换,不追踪内部属性的变化。适合存大对象。

和 React 的根本区别 :Vue 知道"这个组件读了 count,那个组件没读",所以 count 变了只重跑读过的组件。React 默认父组件跑,所有子组件都跟着跑,需要开发者手动用 React.memo 阻断。

第三步:渲染------算出差异,更新最小范围

数据变了 → 渲染函数重新执行 → 产出新的虚拟 DOM 树 → diff 算法对比新旧两棵树。

普通 diff 的写法是:拿到两棵树,挨个节点对比 tag 类型、属性、文本、子节点......每个节点走一遍全流程。但 Vue 不这么干,因为编译阶段已经把"谁会变"标好了。

拿一个带静态内容的模板举例:

html 复制代码
<div>
  <h1>商品详情</h1>              <!-- 永远不变 -->
  <p>{{ price }}</p>             <!-- 只有文本会变 -->
  <span :class="statusClass">    <!-- 只有 class 会变 -->
    {{ statusText }}
  </span>
</div>

编译器处理后的效果(简化版):

js 复制代码
const _hoisted_h1 = createVNode("h1", null, "商品详情")
//  ↑ 静态节点只创建一次,后面永远复用

render() {
  return createVNode("div", null, [
    _hoisted_h1,                                         // 直接用,跳过 diff
    createVNode("p", null, price, 1 /* TEXT */),         // 只比对文本
    createVNode("span", { class: statusClass }, statusText, 2 | 1 /* CLASS + TEXT */)
    //                                                   ↑ 编译器精确标出:class 和文本可能变,别的属性不用管
  ])
}

diff 时就按标记走捷径:

  • 碰到 _hoisted_h1------静态的,跳过整个节点。
  • 碰到 p 标记为 1(TEXT)------只比对新旧文本,class/style/属性一律不看。
  • 碰到 span 标了 2 | 1------只看 class 和文本,跳过 style、属性等其他维度。

这样一来,一个几十个节点的组件树,diff 实际碰的可能就三四个节点,每处也只比对该比的那一两项。开销从"遍历整棵树"变成"精准点射几个点"。这就是编译期分析实打实省出来的性能。


第二章:React

2.1 版本现状

React 最新稳定版是 19.2.7(2026 年 6 月 2 日发布),同日还发了 19.1.8 和 19.0.7 补丁,覆盖三条版本线。

演进时间线:

版本 时间 核心变化 解决了什么
React 16 2017.09 Fiber 架构 渲染可以打断,不再卡界面
React 18 2022.03 并发模式、自动批处理 更新有优先级,用户操作永远先响应
React 19 2024.12 Server Components、Server Actions 组件可以在服务端跑,直接调数据库

React 19 是个分水岭。它新增了 Server Components ------组件只跑在服务端,不打包 JS,能直接读数据库。还有 Server Actions ------前端 <form> 直接调用服务端函数。


2.2 核心原理:从一口气跑完到随时刹车

2.2.1 React 15 的问题:停不下来

React 15 的 diff 算法是递归的。状态一变,从根组件开始往下遍历整棵树,一口气算完所有差异,中间没法停。树大了,JS 线程占满,浏览器卡死------用户点按钮没反应,动画卡帧。

2.2.2 Fiber:造一个能暂停的"栈"

React 16 的解法很巧妙:不用 JS 原生的函数调用栈(不可中断),换成链表。每个组件变成一个 Fiber 节点,三个指针连在一起:

  • child → 第一个子节点
  • sibling → 下一个兄弟节点
  • return → 父节点

React 用 while 循环遍历这个链表,每处理完一个节点就检查:时间片用完了吗?有更紧急的事吗?有就停,没有就继续。

整个工作分成两步:

  • Render 阶段(可暂停):在内存里算出所有差异,不碰真实 DOM
  • Commit 阶段(不能停):把算好的差异一次性写到 DOM

时间切片粒度约 5ms:处理一组工作,用完 5ms 就把控制权还给浏览器。

2.2.3 React 18:给更新排优先级

Fiber 让 React 能刹车,React 18 让它知道什么时候该刹车

  • 并发模式:不要把所有更新一视同仁。用户敲键盘是紧急的,搜索框下面的列表过滤可以慢一点。紧急的更新可以"插队",不紧急的先让路。
  • startTransition:手动标记"这个更新不紧急"。比如搜索时,输入文字的更新定为高优,过滤结果的更新定为低优------打字不卡,列表可以晚半拍。
jsx 复制代码
function SearchPage() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])
  const [isPending, startTransition] = useTransition()

  function handleChange(e) {
    setQuery(e.target.value)              // 高优:立刻更新输入框
    startTransition(() => {
      setResults(filterData(e.target.value)) // 低优:列表可以慢慢来
    })
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <span>过滤中...</span>}
      <ResultList data={results} />
    </>
  )
}

setQuery 直接调,敲字不卡;setResults 包在 startTransition 里,React 会在空闲时再算,如果有新的打字进来,上一次还没算完的过滤就直接丢弃。

  • Suspense :声明式的"还没加载好"状态。<Suspense fallback={<Loading />}><SlowComponent /></Suspense>------SlowComponent 没准备好就自动显示 Loading。
  • 自动批处理 :同一事件回调里多次 setState,React 自动合成一次渲染。React 17 只能在 onClick 这类事件里批处理,setTimeout 或 fetch 回调里三次 setState 就是三次渲染------老代码得用 unstable_batchedUpdates 手动合并。React 18 全场景自动处理了。

2.2.4 React Compiler:自动帮你阻断无效渲染

Vue 能精确知道谁变了,React 做不到------它默认父组件跑,所有子组件跟着跑。所以 React 开发者在代码里大量塞 useMemo/useCallback/React.memo 来手动阻断。

React Compiler 在编译阶段自动帮你干这件事。2025 年 10 月已发布 v1.0,目前处于 RC 阶段,React 官方建议所有项目使用。Next.js 16 已将 reactCompilerexperimental 提升为稳定配置项。不过它并非万能:代码必须遵守 React 规则,不守规则的部分编译器会自动跳过,效果仍因项目而异。


第三章:横向对比

核心差异一句话

维度 Vue 3 React 19
响应式 自动追踪,变了谁通知谁 默认父组件更新则子组件全部重新执行,手动或编译器阻断
渲染 编译期分析模板,精准定位变化 运行时时间切片,保证不长时间卡主线程
组件 .vue 单文件 JSX/TSX 函数
状态管理 Pinia(官方) Zustand / Jotai(社区)
路由 Vue Router(官方) React Router / TanStack Router
SSR Nuxt 3 Next.js

怎么选

  • 团队小、跑得快、不想纠结选型 → Vue。全家桶一路通。
  • 项目大、交互复杂、需要最丰富的生态 → React。选型自由,并发渲染原生支持。
  • 做全栈、重 SEO → 都行。Nuxt 3 和 Next.js 都很成熟,Next.js 的 Server Components 是个独特优势。
  • 做移动端 → React Native 生态更成熟。

趋势

两条路在趋同。Vapor Mode 抛弃虚拟 DOM 走编译路线,React Compiler 在编译期注入优化------都在把运行时的工作搬到编译期。ECMAScript Signals 提案已经 Stage 1,两个框架的响应式思路在标准层面也在靠拢。

版本的更替不会停,但"编译 → 响应式 → 渲染"这条主线八九不离十。


总结一下:

  • Vue 在编译阶段就把模板分析透了,响应式系统做到精准追踪,运行时只动该动的。适合不想纠结太多、快速出活的团队。
  • React 用 Fiber 和并发模式在运行时安排一切,灵活度高、生态最广。需要开发者自己做好性能取舍,但 React Compiler 正在把这件事自动化。
  • 两者正在往同一个方向走------编译器承担更多,运行时越来越轻。

没有最好的框架,只有最适合你当前项目的选择。


如果这篇文章帮你串起了 Vue 和 React 的底层逻辑,欢迎点赞、收藏,方便以后回看。你目前在用哪个框架?觉得它的设计哲学对日常工作影响大不大?评论区聊聊。


参考资料

相关推荐
the_answer1 小时前
CSS 新时代:浏览器原生能力如何重塑前端开发范式
前端
不会写DN1 小时前
固定背景图不随页面滚动的完美方案
前端
天蓝色的鱼鱼1 小时前
Vite 8 换上 Rolldown 后,前端构建真的会快很多吗?
前端·vite
梦曦i1 小时前
全面解析uni-router v1.2.0功能
前端·uni-app
Yiyaoshujuku1 小时前
化学谱图数据API接口,数据字段一览!
linux·服务器·前端
雮尘2 小时前
LangGraph 与 LangSmith 入门教程(JS/TS 版)
前端·人工智能·langchain
英勇无比的消炎药2 小时前
新手必看玩转TinyRobot一定要避开这些坑
前端·vue.js
持敬chijing2 小时前
Web渗透之前后端漏洞-文件上传漏洞-过滤绕过与配置文件漏洞-条件竞争漏洞
前端·安全·web安全·网络安全·网络攻击模型·安全威胁分析
尼斯湖皮皮怪2 小时前
iceCoder:验收门控深度分析
前端·agent