news 2026/9/30 2:43:49

Vue 与 React 响应式原理深度对比:从实现机制到实战选型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue 与 React 响应式原理深度对比:从实现机制到实战选型

一、什么是响应式?

响应式的本质是一句话:

数据变了,用到这个数据的地方自动更新。

听起来简单,但要实现它,需要解决三个问题:

  1. 如何知道数据变了?(数据劫持 / 手动触发)

  2. 如何知道哪些地方用到了这个数据?(依赖收集 / 全量对比)

  3. 如何高效地更新 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 2Vue 3
劫持方式Object.definePropertyProxy
新增/删除属性不支持支持
数组索引/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变化时:

  1. Parent重新执行,生成新的虚拟 DOM 树。

  2. Child也会重新执行(即使它的 props 没变)。

  3. React 对新旧虚拟 DOM 树做 diff。

  4. 只把差异部分应用到真实 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 目前没有的能力。

四、核心差异对比

对比项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 找差异

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 2:43:21

NTP/SNTP时钟协议原理详解:从时间戳到工程避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 2:42:26

拒绝“猜谜式”编程:从 Google PTCF 到 AI 编程五要素框架

前言&#xff1a;在 AI 辅助编程&#xff08;AI Programming&#xff09;日益普及的今天&#xff0c;很多开发者发现&#xff1a;AI 写代码很快&#xff0c;但写出能用的代码很难。 往往是因为我们陷入了“模糊指令”的陷阱。本文将剥离理论概念&#xff0c;直接通过对比案例和…

作者头像 李华
网站建设 2026/9/30 2:42:19

第022篇 try-catch-finally 与 try-with-resources——资源释放正确姿势

摘要:本篇是《Android软件开发面试从入门到精通》第 22 篇,主题为「try-catch-finally 与 try-with-resources」。本篇聚焦「try-catch-finally 与 try-with-resources——资源释放正确姿势」:先回答它解决什么问题,再回答它怎么实现、代价是什么,收尾给出一套可复用的答题…

作者头像 李华
网站建设 2026/9/30 2:42:19

第023篇 自定义异常与异常链——生产代码怎么设计错误

摘要:本篇是《Android软件开发面试从入门到精通》第 23 篇,主题为「自定义异常与异常链——生产代码怎么设计错误」。在Java 核心基础的进度条上,「自定义异常与异常链——生产代码怎么设计错误」承上启下。本篇从零讲起,但按面试官追问的深度推进,读到最后一节就有答案。…

作者头像 李华