一、什么是响应式?
响应式的本质是一句话:
数据变了,用到这个数据的地方自动更新。
听起来简单,但要实现它,需要解决三个问题:
如何知道数据变了?(数据劫持 / 手动触发)
如何知道哪些地方用到了这个数据?(依赖收集 / 全量对比)
如何高效地更新 UI?(精准更新 / 调度渲染)
Vue 和 React 对这三个问题的回答,决定了它们截然不同的设计哲学。
二、Vue 的响应式:自动追踪,精准更新
2.1 核心思想
Vue 的响应式是自动的。你只需要修改数据,Vue 自己知道哪些组件用了这个数据,并自动更新它们。
const state = reactive({ count: 0 }) // 修改数据,UI 自动更新 state.count++开发者不需要手动通知框架“数据变了”,这是 Vue 最核心的体验优势。
2.2 Vue 2:Object.defineProperty
Vue 2 使用Object.defineProperty递归遍历对象的每个属性,将其转换成 getter/setter:
function defineReactive(obj, key, val) { const dep = new Dep() Object.defineProperty(obj, key, { get() { // 依赖收集:谁读了这个属性,就把它记下来 if (Dep.target) { dep.depend() } return val }, set(newVal) { if (newVal === val) return val = newVal // 派发更新:通知所有依赖这个属性的 watcher dep.notify() } }) }工作流程:
渲染组件时读取数据
↓
getter 触发,收集当前组件的 Watcher
↓
数据变化,setter 触发
↓
通知依赖的 Watcher,组件重新渲染
Vue 2 的痛点:
无法检测对象属性的新增和删除(需要
Vue.set/Vue.delete)无法检测数组索引赋值和 length 修改(需要重写数组方法)
递归遍历所有属性,初始化开销大
无法处理 Map、Set 等数据结构
2.3 Vue 3:Proxy
Vue 3 改用Proxy代理整个对象,彻底解决了 Vue 2 的痛点:
function reactive(target) { return new Proxy(target, { get(target, key, receiver) { // 依赖收集 track(target, key) return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver) // 派发更新 trigger(target, key) return result }, deleteProperty(target, key) { const result = Reflect.deleteProperty(target, key) trigger(target, key) return result } }) }Vue 3 的优势:
| 对比项 | Vue 2 | Vue 3 |
|---|---|---|
| 劫持方式 | Object.defineProperty | Proxy |
| 新增/删除属性 | 不支持 | 支持 |
| 数组索引/length | 不支持 | 支持 |
| Map/Set | 不支持 | 支持 |
| 惰性代理 | 否 | 是(访问才代理) |
| 性能 | 初始化慢 | 初始化快 |
2.4 依赖收集:track 和 trigger
Vue 3 内部通过track和trigger完成依赖管理:
// 全局依赖表:targetMap 记录"哪个对象的哪个属性"被"哪些 effect"依赖 const targetMap = new WeakMap() function track(target, key) { if (!activeEffect) return let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } dep.add(activeEffect) } function trigger(target, key) { const depsMap = targetMap.get(target) if (!depsMap) return const dep = depsMap.get(key) if (dep) { dep.forEach(effect => effect()) } }关键点:
每个组件的渲染函数本身就是一个
effect。渲染时读取数据 →
track收集当前 effect。数据变化 →
trigger通知所有依赖的 effect 重新执行。
2.5 精准更新
Vue 的更新粒度是组件级:
<template> <div> <Child /> <p>{{ count }}</p> </div> </template>当count变化时:
Vue 知道只有当前组件依赖了
count。只重新渲染当前组件,
Child不受影响(如果它没依赖count)。在组件内部,通过虚拟 DOM diff 找出具体变化的 DOM 节点。
这就是为什么 Vue 通常不需要useMemo、memo这类手动优化。
三、React 的响应式:手动触发,全量对比
3.1 核心思想
React 的响应式是手动的。你必须调用setState告诉 React“数据变了”,React 才会重新渲染。
const [count, setCount] = useState(0) // 必须手动触发 setCount(count + 1)直接修改数据 React 不会知道:
state.count++ // ❌ React 不会更新 UI setCount(state.count + 1) // ✅3.2 状态不可变
React 推崇不可变数据,状态更新必须创建新对象:
// ❌ 直接修改,React 检测不到 state.list.push(newItem) // ✅ 创建新数组 setList([...list, newItem])为什么要这样做?因为 React 用Object.is做浅比较,只有引用变了才能检测到变化。
3.3 全量 re-render
React 的更新粒度是组件树自顶向下:
function Parent() { const [count, setCount] = useState(0) return ( <div> <Child /> {/* 即使 Child 没变,也会重新 render */} <p>{count}</p> </div> ) }当Parent的count变化时:
Parent重新执行,生成新的虚拟 DOM 树。Child也会重新执行(即使它的 props 没变)。React 对新旧虚拟 DOM 树做 diff。
只把差异部分应用到真实 DOM。
问题:Child白白执行了一遍,浪费性能。
3.4 手动优化
为了避免不必要的子组件渲染,React 提供了这些工具:
// 1. React.memo:props 不变时跳过渲染 const Child = React.memo(function Child({ name }) { return <div>{name}</div> }) // 2. useMemo:缓存计算结果 const expensiveValue = useMemo(() => compute(a, b), [a, b]) // 3. useCallback:缓存函数引用 const handleClick = useCallback(() => { doSomething(id) }, [id])3.5 Fiber 与调度
React 16 引入 Fiber 架构,把渲染工作拆成可中断的小单元:
render 阶段(可中断)
↓
commit 阶段(同步,不可中断)
↓
浏览器绘制
Fiber 让 React 可以:
时间切片:长任务拆成多个时间片,避免阻塞主线程。
优先级调度:用户输入 > 动画 > 数据请求。
高优先级更新可以打断低优先级更新。
这是 Vue 目前没有的能力。
四、核心差异对比
| 对比项 | Vue | React |
|---|---|---|
| 响应式触发 | 自动(Proxy/defineProperty) | 手动(setState) |
| 数据要求 | 可变数据 | 不可变数据 |
| 更新粒度 | 组件级精准更新 | 自顶向下全量 re-render |
| 依赖追踪 | 自动收集 | 无 |
| 优化方式 | 自动 + 编译时优化 | 手动 memo/useMemo |
| 调度能力 | 同步队列,无 Fiber | Fiber 可中断、优先级调度 |
| 心智负担 | 低,开箱即用 | 高,需手动优化 |
| 适合场景 | 快速开发、中小型项目 | 复杂交互、大型应用 |
五、性能表现:谁更快?
这个问题没有绝对答案,取决于场景。
Vue 的优势场景
组件依赖关系明确,精准更新开销小。
编译时优化(Patch Flag、静态提升)减少 diff 成本。
中小型项目开箱即用性能好。
React 的优势场景
大型应用,Fiber 调度避免长任务阻塞。
并发特性(useTransition、Suspense)处理复杂交互。
配合手动优化,性能可以做到极致。
极端场景
超高频更新(如动画):Vue 的细粒度更新通常更快。
超复杂组件树:React 的 Fiber 调度更有优势。
六、总结
| 维度 | Vue | React |
|---|---|---|
| 响应式 | 自动追踪,Proxy | 手动触发,setState |
| 数据 | 可变 | 不可变 |
| 更新 | 组件级精准 | 自顶向下全量 |
| 优化 | 自动 + 编译时 | 手动 memo |
| 调度 | 无 Fiber | Fiber 可中断 |
| 心智 | 低 | 高 |
| 适合 | 快速开发 | 复杂应用 |
Vue 和 React 没有绝对的优劣,只有适合不适合。理解它们的响应式原理,不仅能帮你在面试中脱颖而出,更能让你在实际项目中做出更合理的技术决策。
核心记忆点:
Vue:自动追踪依赖,精准更新组件
React:手动触发更新,全量 diff 找差异