
一、什么是响应式响应式的本质是一句话数据变了用到这个数据的地方自动更新。听起来简单但要实现它需要解决三个问题如何知道数据变了数据劫持 / 手动触发如何知道哪些地方用到了这个数据依赖收集 / 全量对比如何高效地更新 UI精准更新 / 调度渲染Vue 和 React 对这三个问题的回答决定了它们截然不同的设计哲学。二、Vue 的响应式自动追踪精准更新2.1 核心思想Vue 的响应式是自动的。你只需要修改数据Vue 自己知道哪些组件用了这个数据并自动更新它们。const state reactive({ count: 0 }) // 修改数据UI 自动更新 state.count开发者不需要手动通知框架“数据变了”这是 Vue 最核心的体验优势。2.2 Vue 2Object.definePropertyVue 2 使用Object.defineProperty递归遍历对象的每个属性将其转换成 getter/setterfunction 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 3ProxyVue 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 2Vue 3劫持方式Object.definePropertyProxy新增/删除属性不支持支持数组索引/length不支持支持Map/Set不支持支持惰性代理否是访问才代理性能初始化慢初始化快2.4 依赖收集track 和 triggerVue 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-renderReact 的更新粒度是组件树自顶向下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.memoprops 不变时跳过渲染 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 目前没有的能力。四、核心差异对比对比项VueReact响应式触发自动Proxy/defineProperty手动setState数据要求可变数据不可变数据更新粒度组件级精准更新自顶向下全量 re-render依赖追踪自动收集无优化方式自动 编译时优化手动 memo/useMemo调度能力同步队列无 FiberFiber 可中断、优先级调度心智负担低开箱即用高需手动优化适合场景快速开发、中小型项目复杂交互、大型应用五、性能表现谁更快这个问题没有绝对答案取决于场景。Vue 的优势场景组件依赖关系明确精准更新开销小。编译时优化Patch Flag、静态提升减少 diff 成本。中小型项目开箱即用性能好。React 的优势场景大型应用Fiber 调度避免长任务阻塞。并发特性useTransition、Suspense处理复杂交互。配合手动优化性能可以做到极致。极端场景超高频更新如动画Vue 的细粒度更新通常更快。超复杂组件树React 的 Fiber 调度更有优势。六、总结维度VueReact响应式自动追踪Proxy手动触发setState数据可变不可变更新组件级精准自顶向下全量优化自动 编译时手动 memo调度无 FiberFiber 可中断心智低高适合快速开发复杂应用Vue 和 React 没有绝对的优劣只有适合不适合。理解它们的响应式原理不仅能帮你在面试中脱颖而出更能让你在实际项目中做出更合理的技术决策。核心记忆点Vue自动追踪依赖精准更新组件React手动触发更新全量 diff 找差异