Bug随记 —— 对reactive的错误认知

146 阅读2分钟

今天遇到一个这样Bug,我平时很少使用reactive,一般都是使用ref来定义响应式数据;今天突然心血来潮想使用一下reactive。我使用reactive定义了一下响应式数据orderInfo,接口获取数据后再进行赋值,我以为这样可以正常显示内容呢,但结果却与我想的相反,页面没有按预期进行渲染,我就开始疑惑为什么响应性丢失了,不是说Vue3中的Proxy可以监听到对象新增属性吗,可为什么响应性丢失了呢?

// Bug 代码
<template>
  <div>name: {{ orderInfo.name }}</div>
</template>
<script setup lang="ts">
let orderInfo = reactive({});
onMounted(() => {
  // 模拟接口异步请求
  setTimeout(() => {
    orderInfo = { name: '爱情公寓', num: '12138' };
  }, 1000);
});
</script>

经过思考之后,终于想明白reactive确实可以定义响应式数据,而且Proxy也可以监听新增的属性,但这里let orderInfo = reactive({});定义变量后,orderInfo其实就是一个引用数据类型的变量,但它指向的是reactive定义的响应式数据,所以此时它是响应式的,但是如果在接口返回数据后直接,把给orderInfo赋值为一个对象orderInfo = { name: '爱情公寓', num: '12138' };这时候orderInfo的指向的引入地址就变了成功新赋值的对象,这个对象并没有使用reactive包裹,所以它只是一个普通的对象并没有响应性,所以页面也不会自动更新。

那为什么使用 orderInfo.name = '爱情公寓',却可以使页面更新呢?这是因为引用数据类型的值是可以修改,但引用地址并没有变所以orderInfo指向的还是刚开始使用reactive定义的对象,所以响应性还在,从而页面会自动更新。

还有就是刚开始不能定义为 let orderInfo: any = ref(null);,这样页面会报错,因为页面中使用了{{ orderInfo.name }}这样会导致orderInfo.name这里异常,如果是空对象,就不会出现这种问题。

反思:为什么使用ref就可以直接使用对象赋值呢?

答:这是因为refVue内部做了处理,对于ref包裹的值,不管是什么类型的Vue都会先进行一层包裹,然后在丢给Proxy进行代理,所以Proxy代理时,其实代理的是{value:data},所以在赋值时需要使用.value,这样不就也只是改变属性value的值并没有改变引用地址,所以响应性不会丢失。模版中没有使用.value那是因为Vue内部自动做了处理。