首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Vue 3 和 React 的第一个关键差异:状态到底是“可变的”,还是“快照式”的?

Vue 3 和 React 的第一个关键差异:状态到底是“可变的”,还是“快照式”的?

原创
作者头像
peace-free
发布2026-05-16 09:15:53
发布2026-05-16 09:15:53
20
举报
文章被收录于专栏:Vue3Vue3

很多人比较 Vue 3 和 React,喜欢从模板和 JSX 开始讲。这个角度当然直观:Vue 单文件组件里常见的是 <template><script setup>style 三段结构,React 组件里常见的是函数返回 JSX。但如果只停留在写法层面,很容易把两者的差异讲浅了。

真正影响日常开发体验的,不是“一个像 HTML,一个像 JavaScript”这么简单,而是它们对状态变化的理解完全不同。

Vue 3 更像是围绕“响应式数据”组织 UI。你声明一个 refreactive,在组件渲染时读取它,之后它被修改,Vue 就能知道哪些依赖它的地方需要更新。React 则更像是围绕“状态快照”组织 UI。你调用 setStateuseState 返回的 setter,不是直接修改当前这一轮渲染里的变量,而是请求 React 重新调用组件函数,生成下一份 UI 描述。

这不是语法喜好问题,而是两套心智模型。

image-20260516091010324
image-20260516091010324

一、Vue 3 的状态:数据本身带着响应式能力

Vue 3 的响应式基础是 ref()reactive()ref() 通常用来包裹基本类型,也可以包裹对象;reactive() 用来把对象变成响应式代理。Vue 3 官方文档明确说明,reactive() 返回的是一个 JavaScript Proxy,Vue 可以通过代理拦截属性读取和属性修改,从而完成依赖追踪和更新触发。

这句话放到实际开发里,就是下面这种感觉:

代码语言:vue
复制
<script setup>
import { ref } from 'vue'

const count = ref(0)

function add() {
  count.value++
}
</script>

<template>
  <button @click="add">{{ count }}</button>
</template>

在模板里写 {{ count }},Vue 会自动解包,所以你不用写 count.value。在 JavaScript 里操作 ref,需要通过 .value 修改。这个设计刚开始会让一部分 React 开发者不适应,但它的目标很明确:让状态对象本身成为响应式系统的一部分。

如果是对象,Vue 的写法更接近日常 JavaScript:

代码语言:vue
复制
<script setup>
import { reactive } from 'vue'

const form = reactive({
  name: '',
  profile: {
    age: 18
  }
})

function grow() {
  form.profile.age++
}
</script>

在 Vue 3 里,默认情况下这种嵌套对象也是深层响应式的。也就是说,修改 form.profile.age,Vue 能检测到。数组 push、对象属性修改、嵌套字段更新,这些在多数常规场景中都可以直接写。

这也是 Vue 让很多业务开发者觉得“顺手”的原因:你不必每次都拷贝一个新对象,也不必为了改一个深层字段写一长串展开运算符。你面对的仍然像是普通对象,只是这个普通对象被 Vue 的响应式系统接管了。

当然,这里有一个重要前提:你要始终使用 Vue 返回的响应式代理,而不是绕回原始对象。Vue 3 使用 Proxy 后,代理对象和原始对象不是同一个引用。官方文档也提醒过,应该通过响应式代理来访问和修改状态。否则你可能以为自己改了数据,但实际没有触发 Vue 期望的更新路径。

二、React 的状态:每次渲染都是一张快照

React 的状态模型完全不同。React 官方文档把 state 描述为组件的“记忆”,但这个记忆不是一个你能在当前函数调用里随手改掉的普通变量。React 组件函数每次执行,都会拿到当前这一轮渲染对应的 state 值。你调用 setter,例如 setCount(count + 1),本质上是告诉 React:请基于这个更新安排一次新的渲染。

典型写法是这样:

代码语言:jsx
复制
import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)

  function add() {
    setCount(count + 1)
  }

  return <button onClick={add}>{count}</button>
}

这里最容易被初学者误解的是:setCount 调用之后,当前函数里的 count 不会立刻变成新值。React 官方文档用“snapshot”解释这个行为:一次渲染中的 props、state、事件处理函数和局部变量,都是基于那一次渲染计算出来的。你点击按钮时,事件处理函数看到的也是创建它那一轮渲染里的 state。

所以 React 里经常会看到这种写法:

代码语言:jsx
复制
setCount(c => c + 1)

它不是为了显得高级,而是为了避免闭包里拿到旧值。尤其是连续更新、异步回调、定时器、请求返回之后再更新状态时,函数式更新更稳。

React 对对象状态的态度也和 Vue 不一样。React 官方文档明确建议,不要直接修改 state 里的对象,而是创建一个新对象,再把新对象传给 setter。比如:

代码语言:jsx
复制
const [form, setForm] = useState({ name: '', age: 18 })

function changeName(name) {
  setForm({
    ...form,
    name
  })
}

如果直接写 form.name = 'Tom',React 不会因为这个赋值自动知道需要重新渲染。React 关心的是你有没有通过 setter 提交状态更新,以及下一次渲染时 state 引用和内容如何参与比较、渲染和提交。

这背后其实是一种更强调不可变数据的开发习惯。React 没有要求 JavaScript 对象天然不可变,但 React 的状态更新方式鼓励你把每次变化都表达成“产生一个新状态”。

三、同样是更新 UI,Vue 和 React 的触发路径不同

从用户角度看,两者都是点一下按钮,页面上的数字变了。但框架内部的路径不一样。

Vue 3 的路径大致是:组件渲染时读取了响应式数据,Vue 记录这个依赖;响应式数据变化时,Vue 触发依赖它的更新;组件重新执行渲染函数,生成新的虚拟 DOM;渲染器把新旧虚拟 DOM 做对比,然后把必要变化更新到真实 DOM。

React 的路径大致是:事件中调用 state setter;React 安排组件更新;组件函数再次执行,拿到新一轮 state;函数返回新的 JSX;React 对比新旧结果,在 commit 阶段把变化提交到 DOM。

两者都会用到虚拟 DOM,也都会做 diff,但 Vue 3 因为通常掌握模板编译阶段,可以在编译时给运行时提供更多信息。Vue 官方渲染机制文档里提到,Vue 模板会被编译成渲染函数,并且可以通过静态提升、patch flag、树结构打平等方式减少运行时需要遍历和比较的内容。React 的 JSX 更接近运行时的 JavaScript 表达能力,灵活度高,但默认没有 Vue 模板那种同等程度的编译期结构提示。

这不是说 Vue 一定比 React 快,也不是说 React 性能差。真实项目性能取决于组件结构、状态位置、列表规模、缓存策略、打包方式和开发者写法。更准确的说法是:Vue 3 的默认模板路径更倾向于“编译器和运行时配合优化”;React 的默认组件模型更倾向于“通过重新执行组件函数来得到下一份 UI 快照”,再结合 memo、useMemo、useCallback、key 等机制做局部优化。

这个差异会直接影响你写代码时的判断。

在 Vue 里,你可能更自然地把一个对象建成响应式对象,然后在多个函数里直接修改它的字段。在 React 里,你更需要思考状态应该放在哪里,更新时是否要保持引用稳定,某个函数闭包是否拿到了旧值,某个对象或数组是否应该重新创建。

四、Vue 的“可变”不等于随便写,React 的“不可变”也不等于麻烦

有些比较文章会简单下结论:Vue 可以直接改数据,所以简单;React 必须不可变更新,所以繁琐。这个说法有一半道理,但不完整。

Vue 的响应式确实让很多对象更新更直接。比如表单页、后台管理系统、配置面板、弹窗编辑器,这些场景里状态结构常常比较贴近业务对象。使用 reactive 后,直接修改字段非常高效,也很符合直觉。

但 Vue 的“可变”不是没有边界。比如你把响应式对象解构成普通变量,可能会丢失响应式连接;ref 在 JavaScript 中需要 .value;数组和集合中的 ref 解包也有细节;过度依赖深层响应式,可能让状态变化来源变得不够清晰。项目变大后,如果没有约束,任何地方都能改同一个响应式对象,也会增加排查成本。

React 的不可变更新看起来啰嗦,但它带来的好处是变化路径更显式。一个状态怎么从 A 变成 B,通常会集中在 setter、reducer 或状态管理逻辑里。React 的“状态快照”模型也让一些调试工具、时间旅行、撤销重做、状态持久化更容易设计。当然,代价也明显:对象层级深时更新代码会变长;闭包和依赖数组容易出错;如果对渲染机制理解不够,可能会写出大量无意义的重新渲染或缓存代码。

所以更客观的判断应该是:Vue 把很多响应式追踪工作放进框架内部,开发者写起来更接近直接操作数据;React 把状态变化表达得更显式,开发者需要用不可变更新和重新渲染的心智模型来组织代码。

五、Hooks 和 Composition API 看起来像,但底层心智不一样

Vue 3 的 Composition API 经常被拿来和 React Hooks 对比,因为它们都用函数组织逻辑,都可以把状态、生命周期、副作用封装到可复用函数里。比如 Vue 里可以写 useMouse(),React 里也可以写 useMouse()。从“逻辑复用”的角度看,它们确实解决了类似问题。

但它们不是同一种机制。

React Hooks 会在组件每次渲染时按顺序执行。正因为如此,React Hooks 有明确规则:不能在普通条件语句、循环、嵌套函数里随意调用 Hook。否则 React 无法稳定地按照调用顺序匹配每个 Hook 对应的状态。React 官方文档和 ESLint 规则都围绕这套约束展开。

Vue 的 Composition API 通常在 setup<script setup> 中执行,创建响应式状态、计算属性、监听器和生命周期注册。组件更新时,不是重新执行整个 <script setup> 来拿下一张 UI 快照,而是响应式依赖变化后触发对应渲染 effect。Vue 官方 FAQ 也明确提到,Composition API 和 React Hooks 都有逻辑组合能力,但 Vue 是基于可变的细粒度响应式系统,而 React Hooks 在组件更新时会反复调用,并因此产生调用顺序、闭包旧值、依赖数组等问题。

这就是为什么 Vue 里常见的组合函数写法是:

代码语言:js
复制
import { ref, onMounted } from 'vue'

export function useClock() {
  const now = ref(new Date())
  let timer

  onMounted(() => {
    timer = setInterval(() => {
      now.value = new Date()
    }, 1000)
  })

  return { now }
}

而 React 里类似逻辑通常要考虑 effect 依赖和清理函数:

代码语言:jsx
复制
import { useEffect, useState } from 'react'

function useClock() {
  const [now, setNow] = useState(new Date())

  useEffect(() => {
    const timer = setInterval(() => {
      setNow(new Date())
    }, 1000)

    return () => clearInterval(timer)
  }, [])

  return now
}

两段代码都能工作,但你脑子里要想的事情不一样。Vue 里重点是这个 ref 被谁读取、谁修改、生命周期何时注册。React 里重点是 effect 何时执行、依赖数组是否正确、回调里拿到的状态是否是期望的那一轮快照。

六、对团队协作的影响:不是谁更高级,而是谁更适合当前项目

如果团队主要做中后台、表单、数据看板、低代码配置台,Vue 3 的响应式模型通常会让开发速度很快。模板语法清晰,状态修改直接,单文件组件把结构、逻辑、样式放在一个边界里,团队成员即使经验不完全一致,也比较容易形成统一写法。

如果团队前端工程能力强,项目需要大量自定义组件抽象,或者本身技术栈已经围绕 React 生态展开,React 的快照模型和函数组件体系会更有发挥空间。React 的优势不只是库本身,还包括庞大的生态、社区约定、跨端方案,以及围绕状态管理、路由、服务端渲染、编译优化形成的大量实践。

Vue 3 更像是给你一套集成度较高、默认体验顺滑的开发模型。React 更像是给你一个核心足够稳定的 UI 运行模型,再让你通过生态和工程约束搭出自己的应用架构。前者减少了很多选择成本,后者保留了更多组合空间。

如果只问“哪个更好”,这个问题意义不大。更实际的问题应该是:你的团队更容易理解哪套状态模型?你的业务是否经常处理复杂表单和深层对象?你的项目是否需要大量跨平台复用?你的团队是否有能力管理 React 中常见的闭包、依赖数组、memo 边界和状态归属问题?你们是否愿意接受 Vue 响应式系统里代理对象、ref 解包、响应式边界这些规则?

七、一个简单判断:看你更愿意相信“数据”,还是更愿意相信“渲染”

如果用一句话概括这篇文章的核心差异:Vue 3 更强调“数据变了,依赖它的视图更新”;React 更强调“状态更新后,组件重新计算下一份 UI”。

Vue 的开发体验像是在维护一组会自动通知视图的响应式数据。你修改数据,框架追踪依赖并安排更新。React 的开发体验像是在不断描述每个状态下 UI 应该长什么样。你提交状态更新,框架再次调用组件函数,拿到新的描述,再提交变化。

这两种方式都不是银弹。Vue 的响应式让常规业务写起来更直接,但你需要理解代理、ref、解构和响应式边界。React 的状态快照让 UI 描述非常纯粹,但你需要适应不可变更新、闭包快照和 Hooks 规则。

真正成熟的选择,不是站队,而是知道差异在哪里。很多争论之所以没结果,是因为大家表面上在争模板和 JSX,实际上是在用两套完全不同的状态模型评价对方。理解这一点,再去看 Vue 3 和 React 的其它差异,比如组件通信、逻辑复用、性能优化、生态路线、服务端渲染,才不会停留在“我习惯哪个,所以哪个更好”的层面。

下一篇如果继续展开,最值得讲的是模板和 JSX 的差异。那一篇不能只讲语法,而要讲编译器、表达能力、约束边界和团队维护成本。因为 Vue 和 React 真正的分水岭,从来不只是写起来像不像 HTML,而是框架选择把多少复杂度交给编译器,又把多少自由度留给开发者。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Vue 3 的状态:数据本身带着响应式能力
  • 二、React 的状态:每次渲染都是一张快照
  • 三、同样是更新 UI,Vue 和 React 的触发路径不同
  • 四、Vue 的“可变”不等于随便写,React 的“不可变”也不等于麻烦
  • 五、Hooks 和 Composition API 看起来像,但底层心智不一样
  • 六、对团队协作的影响:不是谁更高级,而是谁更适合当前项目
  • 七、一个简单判断:看你更愿意相信“数据”,还是更愿意相信“渲染”
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档