React 状态模型 vs Vue3 响应式模型
对比 React 的 state 快照与更新队列模型,以及 Vue3 的 Proxy、依赖追踪与触发更新模型。
[!info] related notes
- 所属 MOC: React MOC, Vue MOC
- React 主线: react-use-state, react-hooks, React useState 内部原理, react-use-effect, react-state-management-selection, react-performance-overview
- Vue 主线: Vue响应式系统, Vue3 Proxy 响应式原理, Vue中的ref和reactive, vue-computed, Vue中的watch和watchEffect
- 学习路径: React学习路线图, Vue学习路线图
- 相邻主题: Vue vs React 依赖收集机制对比, Vue vs React 触发更新对比, Vue vs React 状态模型核心差异, React中的Context与状态管理边界, react-state-management-selection, ECMAScript原始值与引用值
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)
更准确的理解是:
- 当前渲染里,
count还是旧值 setCount(...)提交了一次更新- 当前事件处理函数继续执行
- React 之后安排下一次渲染
- 下一次渲染里,你才会拿到新的
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 更像“依赖追踪后按需触发”。