React 状态模型 vs Vue3 响应式模型

对比 React 的 state 快照与更新队列模型,以及 Vue3 的 Proxy、依赖追踪与触发更新模型。

#tech / dev / frame #resource / react #resource / vue3 #type / synthesis #status / growing

[!info] related notes

React 状态模型 vs Vue3 响应式模型

这篇笔记只回答一个问题:React 的 setState / useState,和 Vue 3 的响应式系统,在“状态变化后框架到底怎么知道该更新什么”这件事上,核心思路到底差在哪。

一句话定义

  • React 更像“提交状态更新,后续重新执行组件,再得到新 UI”
  • Vue 3 更像“读取时收集依赖,写入时触发依赖,再让相关副作用和组件更新”

React 这边到底更像什么

state 不是你手里那份普通变量

在 React 函数组件里:

const [count, setCount] = useState(0)

这里的 count 更像“当前这次渲染对应的状态快照”,而不是一个你原地改掉后,当前执行上下文就会立刻同步变化的变量。

所以:

setCount(count + 1)
console.log(count)

更准确的理解是:

  1. 当前渲染里,count 还是旧值
  2. setCount(...) 提交了一次更新
  3. 当前事件处理函数继续执行
  4. React 之后安排下一次渲染
  5. 下一次渲染里,你才会拿到新的 count

setCount 更像“提交更新请求”

它不应该被理解成“立刻改掉 state 的函数”,而更像:

  • 把更新放进队列
  • 安排后续渲染
  • 在下次渲染时处理这些更新

所以 React 最值得记住的不是“同步还是异步”,而是:

state 更新是调度和入队,不是当前上下文里的原地赋值。

为什么函数式更新能解决连续更新问题

setCount(count + 1)
setCount(count + 1)

这两次都基于同一个旧快照来算,结果经常只是“都在提交 1”。

而:

setCount(n => n + 1)
setCount(n => n + 1)

提交的是两个更新函数,React 在处理队列时会把前一个结果继续传给后一个,所以最后能得到 2

React 的粗略伪代码心智模型

// 不是源码,只是帮助理解
state = 0

function setCount(action) {
  queue.push(action) // 把更新放进队列
  scheduleRender()   // 安排一次渲染
}

function render() {
  let nextState = state

  for (const action of queue) {
    nextState =
      typeof action === 'function'
        ? action(nextState)
        : action
  }

  state = nextState
  queue = []
  ui = Component(state)
}

这段伪代码表达的重点是:

  • 更新先入队
  • 渲染时处理队列
  • 组件重新执行
  • 用新的 state 重新计算 UI

Vue 3 这边到底更像什么

Vue 3 的核心直觉不是“手动提交 setState”,而是:

  • 读的时候收集依赖
  • 写的时候触发依赖
  • 由响应式系统通知相关 effect / 组件更新

为什么说是 Proxy

reactive() 返回的是代理对象。你后面读写的其实是代理后的版本:

const state = reactive({ count: 0 })

读的时候可以拦截 get,写的时候可以拦截 set

为什么说像发布订阅,但更准确叫依赖追踪

可以把它粗略理解成:

  • 有人读取了 state.count,Vue 记下“谁依赖了它”
  • 有人写入 state.count = 1,Vue 就通知依赖它的 effect / 组件重新执行

所以“发布订阅”这个比喻可以帮助理解,但 Vue 这套机制更准确的说法是:

  • 依赖追踪
  • 响应式 effect
  • track() / trigger() 机制

Vue 3 的粗略伪代码心智模型

// 不是源码,只是帮助理解
const bucket = new Map()

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
    }
  })
}

function effect(fn) {
  activeEffect = fn
  fn() // 执行时触发 get,从而收集依赖
  activeEffect = null
}

这段伪代码的重点是:

  • 读时知道“谁依赖了什么”
  • 写时知道“该通知谁”

两边最核心的差异

1. 状态更新入口不同

React:

setCount(c => c + 1)

Vue 3:

state.count++
count.value++

React 更强调“通过更新入口提交下一次状态”,Vue 更强调“直接改响应式数据,由系统负责通知依赖更新”。

2. 依赖关系的建立方式不同

React 默认不是靠“谁读了哪个字段”自动建立细粒度依赖图。

它的默认模型更接近:

  • 组件的 state / props 变化了
  • 组件重新执行
  • 子树跟着重新计算

而 Vue 更接近:

  • 首次运行时记录“这个渲染 / effect 实际读了哪些响应式值”
  • 这些值变化时,再触发对应 effect / 组件更新

3. 为什么 React 更强调不可变更新,Vue 没那么强调

React 更像“把下一次状态当作新值提交进去”,所以你常会写:

setUser(prev => ({ ...prev, age: prev.age + 1 }))
setList(prev => [...prev, item])

Vue 3 因为有 Proxy,像下面这样原地改通常就是合法的:

state.user.age++
state.list.push(item)

因为 setter 会被拦截,Vue 可以知道你改了哪一项,再去触发对应依赖更新。

4. 同步流程里读到旧值的体验不同

React:

setCount(count + 1)
console.log(count) // 常常还是旧值

这是“state 快照 + 更新入队”模型的自然结果。

Vue 3:

count.value++
console.log(count.value)

同一个同步流程里你通常就能读到改后的值,因为你改的是响应式容器当前持有的数据本身;只是 DOM 更新未必会在这一刻立刻完成。

最适合记住的粗暴版本

  • React 更像“替换状态,重新渲染组件”
  • Vue 更像“代理数据,追踪依赖,按需触发更新”

一段可以直接放进笔记里的短版本

setState 的原理是什么

在 React 里,setState / setXxx 做的不是“立刻修改当前变量”,而是提交一次状态更新。React 会把更新放进队列,并在后续重新渲染组件时处理这些更新。函数组件里读到的 state,更像是“当前这次渲染的快照”,所以在同一次执行里调用 setCount() 后立刻读 count,通常还是旧值。

和 Vue 3 的区别

Vue 3 的响应式系统更接近“Proxy + 依赖追踪 + 触发更新”。组件渲染时,Vue 会记录它实际读取了哪些响应式数据;这些数据变化后,Vue 会触发相关组件或副作用重新执行。也可以把它粗略理解成一种更细粒度的“发布订阅”。

一个直观对比

  • React:调用 setState,提交更新,下一次渲染拿到新 state
  • Vue 3:直接修改响应式数据,Proxy 拦截变化并通知依赖更新

理解上的区别

  • React 更强调:state 是渲染快照
  • Vue 3 更强调:state 是可被追踪的响应式对象

最短记忆方式

React 更像“更新入队后重渲染”,Vue 更像“依赖追踪后按需触发”。

创建于 2026/3/24 更新于 2026/5/27