Vue 3 响应式系统中,reactive 和 shallowReactive 的区别是什么?如何手动触发响应式更新?

大白话 Vue 3 响应式系统中,reactive 和 shallowReactive 的区别是什么?如何手动触发响应式更新?

前端小伙伴们,有没有遇到过这种情况?用reactive定义了一个对象,改深层属性页面没反应;或者明明只需要改顶层数据,却因为reactive递归代理导致性能浪费?今天咱们就聊聊Vue 3响应式系统的"深浅双煞"——reactiveshallowReactive,手把手教你选对API,还能手动触发更新!看完这篇,你不仅能避开坑,还能和面试官唠明白底层逻辑~

一、响应式的"过犹不及"之痛

先讲个我上周踩的坑:做一个商品详情页,用reactive定义了商品信息对象,其中specs(规格)是个数组,里面存着{ name: '颜色', value: '红色' }这样的深层对象。结果发现,**直接修改specs[0].value**时,页面没跟着变!查了半天才知道,原来reactive的递归代理有"隐藏条件"。

另一个坑是做后台表单,表单数据是顶层字段(如usernameemail),没有深层结构,但用reactive后发现,每次修改都触发递归代理,明明不需要深层响应,却平白浪费性能。这就是典型的"响应式过犹不及"——该响应的没响应,不该耗的性能耗了

二、响应式系统的"深浅之道"

要解决上面的问题,得先明白Vue 3响应式系统的核心——Proxy代理。它就像一个"数据侦探",监听数据的变化并通知视图更新。但reactiveshallowReactive的"侦探范围"不同。

1. reactive:递归代理的"全能侦探"

reactive的作用是将对象转换为深层响应式对象。它通过Proxy代理对象的所有属性,并且递归代理嵌套的对象/数组。简单说就是:

  • 顶层属性被代理(修改顶层触发更新);
  • 深层属性(如对象里的对象、数组里的对象)也被代理(修改深层同样触发更新)。

原理
Vue 3在reactive内部会检查属性值的类型,如果是对象/数组,就递归调用reactive,直到遇到原始类型(string/number等)或已代理过的对象。

2. shallowReactive:浅层代理的"边界侦探"

shallowReactive则是浅层响应式对象。它同样用Proxy代理顶层属性,但不会递归代理深层属性。也就是说:

  • 顶层属性被代理(修改顶层触发更新);
  • 深层属性保持原样(修改深层不触发更新,只是普通对象的修改)。

原理
shallowReactiveProxy拦截器只处理顶层的getset,深层属性的访问/修改不会触发依赖收集或更新通知。

3. 响应式触发条件:依赖收集与触发

Vue的响应式系统分两步:

  1. 依赖收集:组件渲染时访问响应式数据,会将组件的effect(副作用函数)收集到该数据的依赖集合中;
  2. 触发更新:修改响应式数据时,遍历依赖集合,触发effect重新执行(重新渲染组件)。

reactive的深层代理能确保深层属性的修改触发依赖收集和更新,而shallowReactive的深层属性修改不会触发这两步,因此视图无变化。

三、代码示例:从"没反应"到"丝滑更新"

示例1:reactive的深层响应(正常情况)

先看reactive的表现:修改深层属性,视图会更新吗?

// 示例1:reactive的深层响应
import { reactive, effect } from 'vue';

// 用reactive创建深层响应式对象
const product = reactive({
  name: '手机',
  specs: [{ name: '颜色', value: '白色' }]
});

// 模拟组件渲染(effect会收集依赖)
const updateView = effect(() => {
  console.log(`当前颜色:${product.specs[0].value}`);
});

// 修改深层属性(specs数组中第一个对象的value)
product.specs[0].value = '黑色'; 
// 输出:当前颜色:黑色(视图更新)

现象:修改深层属性specs[0].valueeffect重新执行,视图更新。因为reactive递归代理了specs数组和里面的对象,深层修改触发了依赖。

示例2:shallowReactive的浅层响应(修改深层无变化)

再看shallowReactive的表现:同样修改深层属性,视图会更新吗?

// 示例2:shallowReactive的浅层响应
import { shallowReactive, effect } from 'vue';

// 用shallowReactive创建浅层响应式对象
const product = shallowReactive({
  name: '手机',
  specs: [{ name: '颜色', value: '白色' }]
});

// 模拟组件渲染
const updateView = effect(() => {
  console.log(`当前颜色:${product.specs[0].value}`);
});

// 修改深层属性(specs数组中第一个对象的value)
product.specs[0].value = '黑色'; 
// 无输出(视图未更新)

// 修改顶层属性(name)
product.name = '新手机'; 
// 输出:当前颜色:黑色(因为effect重新执行,读取到了新的name,但specs的修改未触发更新)

现象

  • 修改深层属性specs[0].value:无日志输出,视图不更新(深层属性未被代理);
  • 修改顶层属性name:日志输出,视图更新(顶层属性被代理)。

示例3:手动触发shallowReactive的深层更新(关键技巧)

如果用了shallowReactive,但想手动触发深层属性的更新,该怎么办?可以用triggerRef或修改顶层属性"捎带"更新:

// 示例3:手动触发shallowReactive的深层更新
import { shallowReactive, effect, triggerRef, ref } from 'vue';

const product = shallowReactive({
  name: '手机',
  specs: [{ name: '颜色', value: '白色' }]
});

// 用ref创建一个"触发器"(用于手动触发更新)
const trigger = ref(0);

// 模拟组件渲染(依赖trigger)
const updateView = effect(() => {
  // 强制依赖trigger,trigger变化时effect重新执行
  trigger.value; 
  console.log(`当前颜色:${product.specs[0].value}`);
});

// 修改深层属性后,手动触发trigger
product.specs[0].value = '黑色';
trigger.value++; // 输出:当前颜色:黑色(视图更新)

四、reactive vs shallowReactive

用表格对比两者的差异,帮你快速选对API:

对比项 reactive shallowReactive
响应深度 深层响应(递归代理所有子属性) 浅层响应(仅代理顶层属性)
修改深层属性 触发视图更新 不触发视图更新(需手动触发)
性能消耗 较高(递归代理耗性能) 较低(仅代理顶层)
适用场景 深层数据频繁修改(如嵌套表单) 顶层数据修改,深层数据只读/少变
内存占用 较高(存储所有深层依赖) 较低(仅存储顶层依赖)
手动更新难度 无需(自动响应) 需配合triggerRef或修改顶层属性

五、面试题回答方法

正常回答(结构化):

reactiveshallowReactive是Vue 3中创建响应式对象的API,核心区别在于响应深度:

  1. reactive:递归代理对象的所有属性(包括深层嵌套的对象/数组),修改任意层级属性都会触发视图更新;
  2. shallowReactive:仅代理顶层属性,深层属性保持普通对象特性(修改深层不触发更新);
  3. 手动触发更新:对于shallowReactive的深层属性,可通过triggerRef创建触发器,或修改顶层属性间接触发更新;
  4. 选择依据:需要深层响应(如嵌套表单)用reactive,需要性能优化(如顶层数据)或深层数据只读用shallowReactive。”

大白话回答(接地气):

reactive就像快递的‘全程追踪’——从包裹到各个小零件都贴上追踪标签,哪怕你拆了里面的小盒子(深层属性),系统也能知道并通知你(触发更新)。
shallowReactive则是‘顶层追踪’——只给大箱子(顶层属性)贴标签,里面的小盒子(深层属性)没标签,你拆小盒子系统不知道(不触发更新)。
要是用了shallowReactive又想让系统知道小盒子被拆了,就得手动喊一嗓子(triggerRef),告诉系统‘小盒子变了,该更新啦’~”

六、总结:3个选择原则+2个手动更新技巧

3个选择原则:

  1. 深层数据必改→用reactive(如商品规格、嵌套评论);
  2. 深层数据只读/少改→用shallowReactive(如静态配置、一次性加载的深层数据);
  3. 性能敏感场景→用shallowReactive(减少递归代理的性能消耗)。

2个手动更新技巧:

  1. triggerRef触发器:创建一个ref变量,修改深层属性后更新ref的值,强制触发依赖它的effect
  2. 修改顶层属性:在shallowReactive对象中添加一个“版本号”顶层属性(如version),修改深层属性后递增version,间接触发更新:
    const product = shallowReactive({
      version: 1, // 顶层版本号
      specs: [{ value: '白色' }]
    });
    
    // 修改深层属性后
    product.specs[0].value = '黑色';
    product.version++; // 触发视图更新
    

七、扩展思考:4个高频问题解答

问题1:shallowReactive可以替代reactive吗?

解答:不能。shallowReactive仅适用于深层数据不需要响应的场景。如果深层数据需要频繁修改(如用户输入的嵌套表单),必须用reactive,否则视图不会更新,导致用户体验差。

问题2:手动触发更新有性能问题吗?

解答:合理使用没有问题。triggerRef和修改顶层属性的本质是触发effect重新执行,只要effect内部逻辑简单(如读取数据),性能消耗可以忽略。但如果effect包含复杂计算,建议直接用reactive避免手动操作。

问题3:reactive和ref有什么区别?

解答

  • reactive用于对象/数组(深层响应);
  • ref用于原始类型(string/number等)或对象(通过.value访问,内部用reactive实现);
  • shallowReactivereactive的浅层版本,shallowRefref的浅层版本(仅.value变化触发更新,深层属性不响应)。

问题4:Vue 2的Object.defineProperty和Vue 3的Proxy有什么关系?

解答
Vue 2用Object.defineProperty拦截属性的get/set,但无法检测数组索引修改和对象新增属性;
Vue 3用Proxy代理整个对象,能拦截更多操作(如deletePropertyhas等),且reactive的递归代理解决了深层响应问题。shallowReactive则是在Proxy基础上取消了递归,实现浅层响应。

结尾:用对响应式API,让应用"该动才动"

reactiveshallowReactive是Vue 3响应式系统的"左右护法",选对API能让你的应用既流畅又高效。记住:**深层修改用reactive,浅层操作选shallowReactive,手动更新靠triggerRef**~

下次遇到响应式不更新的问题,别忘了这篇文章!如果帮你理清了思路,记得点个赞,咱们下期,不见不散!

你可能感兴趣的:(大白话前端八股,vue.js,前端,javascript)