← [ BACK_TO_NOTES ]

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

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

#frame #react #vue3 #synthesis #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 更像“代理数据,追踪依赖,按需触发更新”

学习检查

不看正文回答:

  1. React state 为什么更适合理解成“某次 render 的 snapshot”,而不是可被原地修改的变量?
  2. setState / setter 为什么是提交 update,而不是立刻修改当前闭包里的值?
  3. Vue3 的 Proxy、track、trigger 分别处在哪个响应式阶段?
  4. React 与 Vue 在“依赖是谁声明/收集的”这件事上有什么根本差异?
  5. 为什么 React 更强调 immutable update,而 Vue 对 reactive object 的原位修改更自然?
  6. 为什么 Vue state 同步可读到新值,但 DOM 仍可能要等 next tick?
  7. 给一个连续三次自增场景,分别解释 React functional update 与 Vue ref update 的执行模型。
  8. 给一个“父组件只使用对象中的一小部分字段”的场景,比较 React/Vue 如何决定哪些工作需要重新执行。

最短记忆方式

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

创建于 2026/3/24 更新于 2026/9/30
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本