vue和react的响应式有什么区别?(上)
Vue 和 React 都能做到“数据变化后更新页面”,但它们感知变化的方式不同:
Vue 追踪“哪些响应式属性被读取和修改”;React 追踪“哪个组件调用了状态更新函数”。
可以先看这张核心对比表:
一、React:显式通知式响应
React 不会监听普通变量或对象属性的修改。
function Counter() { let count = 0 function add() { count++ } return <button onClick={add}>{count}</button> }点击以后,变量确实变了,但页面不会更新,因为 React 不知道发生了变化。
必须调用 React 提供的 setter:
function Counter() { const [count, setCount] = useState(0) function add() { setCount(count + 1) } return <button onClick={add}>{count}</button> }setCount做两件事:
记录下一次状态 + 请求 React 重新渲染当前组件下一次渲染时,React 会重新调用Counter(),生成新的 JSX,再把新旧结果进行协调比较。React 官方将这一过程划分为触发渲染、执行渲染和提交 DOM 三个阶段。
可以简化成:
setCount(1) ↓ React 安排更新 ↓ Counter() 再执行一次 ↓ 得到新的 JSX ↓ 比较新旧 UI ↓ 更新必要的 DOM二、Vue:依赖追踪式响应
Vue 会把状态转换成响应式对象:
import { reactive } from 'vue' const state = reactive({ count: 0 })Vue 3 对响应式对象主要使用Proxy。简化原理如下:
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 } }) }当组件渲染时读取:
state.countVue 的get拦截器会记录:
当前组件依赖 state.count以后执行:
state.count++set拦截器会通知之前依赖state.count的响应式 effect,组件随后重新渲染。Vue 3 对响应式对象使用 Proxy,对ref的.value使用 getter/setter,并通过track和trigger完成依赖收集及更新触发。
过程可以简化为:
组件读取 state.count ↓ Vue 记录依赖关系 ↓ state.count++ ↓ Proxy 拦截修改 ↓ 通知依赖它的组件 ↓ 组件重新渲染三、最大的区别:谁负责告诉框架发生了变化
React:开发者显式通知
setCount(count + 1)React 不关心你之前读取了哪些普通变量。它只知道:
某个 state setter 被调用了 当前组件需要重新计算Vue:框架自动追踪
count.value++或者:
state.count++Vue在组件渲染过程中已经记录了:
这个组件读取过 count所以 count 修改后,它知道应该通知哪些响应式 effect。
因此可以概括为:
React:事件驱动组件更新 Vue:依赖驱动组件更新不过两者最后通常都会重新执行相应组件的渲染逻辑,并同步必要的 DOM 变化。
四、对象更新方式不同
这是实际开发中最明显的区别之一。
Vue 可以直接修改响应式对象
<script setup> import { reactive } from 'vue' const user = reactive({ name: 'Alice', age: 20 }) function rename() { user.name = 'Bob' } </script> <template> <button @click="rename"> {{ user.name }} </button> </template>执行:
user.name = 'Bob'会经过 Proxy 的set拦截,因此可以触发更新。
React 通常不能直接修改状态对象
错误写法:
const [user, setUser] = useState({ name: 'Alice', age: 20 }) function rename() { user.name = 'Bob' setUser(user) }这里传回去的仍然是原来的对象:
Object.is(oldUser, newUser) // trueReact 可能认为状态没有变化并跳过更新。
应创建新对象:
function rename() { setUser(previous => ({ ...previous, name: 'Bob' })) }React 官方建议把 state 中的对象视为只读数据,通过创建并传入新对象完成更新,而不是直接修改原对象
所以:
Vue:以可变响应式对象为主 React:以状态快照和不可变更新为主五、更新粒度有什么区别
假设状态是:
const state = { name: 'Alice', age: 20, address: { city: 'Beijing' } }Vue 的依赖追踪更细
如果模板只读取:
<p>{{ state.name }}</p>Vue 会追踪对state.name的读取。
修改:
state.age++不会触发所有读取state的响应式逻辑;Vue知道修改的是age,而不是name。
但要注意:
Vue知道哪些属性触发了组件的渲染 effect,不代表它直接绕过组件重新渲染、只修改一个表达式。
当前 Vue 默认渲染系统仍会重新执行相关组件的渲染 effect,然后借助虚拟 DOM 和编译器生成的优化信息减少比较及 DOM 操作。
React 的触发单位更偏向组件
const [user, setUser] = useState({ name: 'Alice', age: 20 })调用:
setUser(previous => ({ ...previous, age: previous.age + 1 }))React知道的是:
这个组件的 user 状态更新了