vue3中使用reactive定义的变量响应式丢失问题

一、前言

在Vue 3中,可以使用reactive函数将普通JavaScript对象转换为响应式对象,这样当对象的属性发生变化时,就会自动更新相应的UI。

但使用 reactive 时,如果不当使用,可能导致响应性失效,带来一些困扰。这可能让开发者在愉快编码的同时,突然发现某些操作失去了响应性,不明所以。因此,建议在不了解 reactive 失去响应的情况下慎用,而更推荐使用 ref。

二、reactive和 ref 对比

reactiveref
❌ 只支持对象和数组(引用数据类型)✅ 支持基本数据类型 + 引用数据类型
✅ 在 <script> <template> 中无差别使用❌ 在 <script><template> 使用方式不同(在 <script> 中要使用 .value)
❌ 重新分配一个新对象会丢失响应性✅ 重新分配一个新对象不会失去响应
能直接访问属性需要使用 .value 访问属性
❌ 将对象传入函数时,失去响应✅ 传入函数时,不会失去响应
❌ 解构时会丢失响应性,需使用 toRefs❌ 解构对象时会丢失响应性,需使用 toRefs

即:

  • ref 用于将基本类型的数据和引用数据类型(对象)转换为响应式数据,通过 .value 访问和修改。
  • reactive 用于将对象转换为响应式数据,可以直接访问和修改属性,适用于复杂的嵌套对象和数组。

三、响应式丢失的情况

3.1. 出现原因

  • ref 定义数据(包括对象)时,都会变成 RefImpl(Ref 引用对象) 类的实例,无论是修改还是重新赋值都会调用 setter,都会经过 reactive 方法处理为响应式对象。
  • 但是 reactive 定义数据(必须是对象),是直接调用 reactive 方法处理成响应式对象。如果重新赋值,就会丢失原来响应式对象的引用地址,变成一个新的引用地址,这个新的引用地址指向的对象是没有经过 reactive 方法处理的,所以是一个普通对象,而不是响应式对象。解构同理。

3.2. 对使用reactive 函数定义的变量直接赋值

  • 对使用reactive 函数定义的变量直接赋值(重新分配一个新对象会丢失响应性)
<script setup>
import { reactive } from 'vue';

// 定义一个响应式变量
const data = reactive({
  name: "",
  age: ""
});

// 请求接口
axios.get('/api/data')
  .then(res => {
    // 直接赋值
    data = res.data;
  })
  .catch(err => console.log(err));

</script>

3.2.1 解决方法

3.2.1.1. 逐个属性进行赋值(不推荐!!!)
<script setup>
​import { reactive } from 'vue';
 
// 定义一个响应式变量
const data = reactive ({
    name:"",
    age:""
}});
 
// 请求接口
axios.get('/api/data')
  .then(res => {
    // 逐个属性赋值 不推荐
    data.name= res.data.name;
    data.age= res.data.age;
  })
  .catch(err => console.log(err));</script>
3.2.1.2. 使用 Object.assign() (有些情况不适用)
<script setup>
​import { reactive } from 'vue';
 
// 定义一个响应式变量
const data = reactive ({
    name:"",
    age:""
}});
 
// 请求接口
axios.get('/api/data')
  .then(res => {
    //使用 Object.assign 不会丢失响应式
    data = Object.assign(data , res.data)
  })
  .catch(err => console.log(err));</script>
3.2.1.3. 使用ref()来进行定义(最简单)
<script setup>
​import { ref } from 'vue';
 
// 定义一个响应式变量
const data = ref ({
    name:"",
    age:""
});
 
// 请求接口
axios.get('/api/data')
  .then(res => {
    // 更新响应式变量的值
    data.value = res.data;
  })
  .catch(err => console.log(err));</script>

上述代码中,data变量通过ref函数定义为响应式变量,它的值是一个对象。当请求接口完成时,将响应的数据赋值给data.value,就会自动更新相应的UI。

3.2.1.4. 直接在reactive中嵌套一层
<script setup>
​import { reactive } from 'vue';
 
// 定义一个响应式变量
const data = reactive ({
    dataObj:{
        name:"",
        age:""
    }
});
 
// 请求接口
axios.get('/api/data')
  .then(res => {
    // 嵌套一层 dataObj
    data.dataObj= res.data;
  })
  .catch(err => console.log(err));</script>

使用reactive函数将data转换为响应式对象。这样在后续更新data对象的dataObj属性时,,就会自动更新相应的UI。

3.2.1.5. 如果有ts类型限制可以写类(TS对reactive里对象进行限制)
  • 单独拿出来一个ts文件,比如user.ts
//1.定义限制userData的接口
export interface userInfo {
  name: string,
  age: number
}
//写类
export class data {
  //定义userData并且做TS限制和赋初始值
  userData: userInfo = {
    name: "",
    age: ""
  }
}
  • 在对应的.vue文件中引入该类。
//1.引入刚写好ts类文件
import { userInfo, data } from "@/type/user.ts"
//2.重点来了,我实例化出来data,然后用一个变量User接收。
let User = reactive(new data());
/*
//实例化出来以后相当于这样的结构:
User={
    userInfo:{
        name:"",
        age:""
    }
}
*/
//3.获取接口数据
axios.get('/api/data')
  .then(res => {
    // 更新响应式变量的值
    User.userData = res.data;//将返回的结果赋值给data,这样也不会丢失响应式,并且userData也受了TS的限制。
  })
  .catch(err => console.log(err));

3.3. 解构赋值引起响应式数据丢失

在Vue中,使用reactive定义变量时,需要注意解构赋值的情况。如果在解构赋值中使用reactive定义的变量,会导致数据丢失,因为解构赋值会创建一个新的引用,而不是原始对象。因此,我们应该避免在解构赋值中使用reactive定义的变量,或者使用拷贝或者toRefs来避免数据丢失。

<script setup>
import { reactive } from 'vue';

// 定义一个响应式变量
const data = reactive({
  name: "码农键盘上的梦",
  age: "99"
})

// 解构了  响应式也丢了
let { name } = data; //解构赋值

</script>

3.3.1. 解决方案

3.3.1.1. 直接访问reactive定义的变量,而不是使用解构赋值;
3.3.1.2. 使用toRefs方法将响应式对象转化为普通对象的响应式属性;
<script setup>
import { reactive, toRefs } from 'vue'


// 定义一个响应式变量
const data = reactive({
  name: "码农键盘上的梦",
  age: "99"
})

// 使用toRefs解决
const { name, age } = toRefs(data)

</script>

这种方法使用toRefs方法将响应式对象转化为普通对象的响应式属性是较为常用的方法。

3.3.1.3. 在解构赋值时使用拷贝来避免数据丢失;
<script setup>
import { reactive, toRefs } from 'vue'


// 定义一个响应式变量
const data = reactive({
  name: "码农键盘上的梦",
  age: "99"
})

// 使用拷贝解决
const { name: nameCopy, age: ageCopy } = { ...data }
console.log(nameCopy, ageCopy)

</script>

四、官方文档对reactive的解读

reactive() API 有一些局限性:

  • 有限的值类型:它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型。
  • 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失:
let state = reactive({ count: 0 })
 
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })
  • 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:
const state = reactive({ count: 0 })
 
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
 
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)

由于这些限制,我们建议使用 ref() 作为声明响应式状态的主要 API。

  • 25
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Vue3,我们可以使用ref和reactive定义响应式数据。然而,有些情况下会导致响应式丢失问题。其reactive丢失响应式的情况有两种。 第一种情况是直接赋值。当我们定义一个数据并使用reactive进行处理后,如果我们直接将一个新的值赋给这个数据,就会导致响应式丢失。这是因为重新赋值后,这个数据的引用地址会发生变化,指向一个没有经过reactive处理的普通对象,而不是一个响应式对象。所以,我们需要避免直接赋值给reactive定义的数据。 第二种情况是解构赋值。当我们对一个使用reactive处理的数据进行解构赋值时,同样会导致响应式丢失。解构赋值会创建一个新的引用地址,指向一个没有经过reactive处理的普通对象。因此,我们在使用解构赋值时也需要注意避免丢失响应式。 为了避免这些问题,我们可以使用toRefs函数将reactive对象转换为响应式的引用对象,这样即使进行重新赋值或解构赋值,也能保持响应式的特性。 总结起来,为了避免在Vue3丢失响应式,我们需要注意避免直接赋值和解构赋值,可以使用toRefs函数来保持响应式的特性。 #### 引用[.reference_title] - *1* *2* *3* [避大坑!Vue3reactive丢失响应式问题](https://blog.csdn.net/Yan9_9/article/details/128617371)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值