vue3中使用reactive定义的变量响应式丢失问题(大坑!!!)

前言

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

但是请注意以下情况可能会丢失数据的响应式:

响应式丢失的情况:

1、对使用reactive 函数定义的变量直接赋值

javascript 复制代码
<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>

解决方法如下:

1、逐个属性进行赋值(不推荐!!!)
javascript 复制代码
​<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>
2、改用ref(最简单) 简单数据类型使用ref()来进行定义。
javascript 复制代码
​<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.直接在reactive中嵌套一层
javascript 复制代码
​<script setup>
​import { reactive } from 'vue';

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

// 请求接口
axios.get('/api/data')
  .then(res => {
    // 嵌套一层 dataArr
    data.dataObj= res.data;
  })
  .catch(err => console.log(err));

​</script>

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

4、如果有ts类型限制可以写类(TS对reactive里对象进行限制)

单独拿出来一个ts文件,比如user.ts

javascript 复制代码
//1.定义限制userData的接口
export interface userInfo{
    name:string,
    age:number
}
//写类
export class data{
    //定义userData并且做TS限制和赋初始值
    userData:userInfo = {
        name:"",
        age:""
    }
}

在对应的.vue文件中引入该类。

javascript 复制代码
//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));

2、解构赋值引起响应式数据丢失

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

javascript 复制代码
​<script setup>
import { reactive } from 'vue';

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

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

</script>

以下是几种解决方法:

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


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

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

</script>

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

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


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

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

</script>

3、原理:

1.ref 定义数据(包括对象)时,都会变成 RefImpl(Ref 引用对象) 类的实例,无论是修改还是重新赋值都会调用 setter,都会经过 reactive 方法处理为响应式对象。

2.但是 reactive 定义数据(必须是对象),是直接调用 reactive 方法处理成响应式对象。如果重新赋值,就会丢失原来响应式对象的引用地址,变成一个新的引用地址,这个新的引用地址指向的对象是没有经过 reactive 方法处理的,所以是一个普通对象,而不是响应式对象。解构同理。

附:官方文档对reactive的解读

reactive() API 有一些局限性:

  1. 有限的值类型 :它只能用于对象类型 (对象、数组和如 MapSet 这样的集合类型)。它不能持有如 stringnumberboolean 这样的原始类型

  2. 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地"替换"响应式对象,因为这样的话与第一个引用的响应性连接将丢失:

    javascript 复制代码
    let state = reactive({ count: 0 })
    
    // 上面的 ({ count: 0 }) 引用将不再被追踪
    // (响应性连接已丢失!)
    state = reactive({ count: 1 })
  3. 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:

    js

    javascript 复制代码
    const state = reactive({ count: 0 })
    
    // 当解构时,count 已经与 state.count 断开连接
    let { count } = state
    // 不会影响原始的 state
    count++
    
    // 该函数接收到的是一个普通的数字
    // 并且无法追踪 state.count 的变化
    // 我们必须传入整个对象以保持响应性
    callSomeFunction(state.count)

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

注:未经允许,不可转载!

相关推荐
Martin -Tang28 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发29 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
老码沉思录1 小时前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂2 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html