使用ref定义基本数据类型时,在脚本里使用时,需要加.value后缀,然而在模板里不需要,这是因为Vue3会自动帮你加上,这就使得ref相比reactive更加简单

let num = ref(0) // 定义
let isShow = ref(false)  // 定义

const onChange = () => {
	num.value++  // js使用
	isShow.value = true  // js使用
}
<!-- Vue3模板引用使用 -->
<a-modal v-model:visible="isShow"></a-modal>

1、ref和reactive定义数组对比

所以,vue2并不是不能监测数组的变化,

使用ref定义数组举例如下

const tableData = ref([]) // 定义

const getTableData = async () => {
	const { data } = await getTableDataApi() // 模拟接口获取表格数据
	tableData.value = data // 修改
}
<!-- Vue3模板引用使用 -->
<a-table v-model:dataSource="tableData"></a-table>

 图中以我们常用的表格数据举例,可以看到,ref定义数组与定义基本数据类型没什么差别,接下来看看reactive

const tableData = reactive([]) // 定义

const getTableData = async () => {
	const { data } = await getTableDataApi() // 模拟接口获取表格数据
	tableData = data // 修改,错误示例,这样赋值会使tableData失去响应式
}

需要注意的是,使用 tableData = data 的修改方式会造成 tableData 响应式丢失,解决方法如下(供参考)

// 方法一:改为 ref 定义
const tableData = ref([])
const getTableData = async () => {
	const { data } = await getTableDataApi()
	tableData.value = data // 使用.value重新赋值
}
// 方法二:使用 push 方法
const tableData = reactive([])
const getTableData = async () => {
	const { data } = await getTableDataApi()
	tableData.push(...data) // 先使用...将data解构,再使用push方法
}
// 方法三:定义时数组外层嵌套一个对象
const tableData = reactive({ list:[] })
const getTableData = async () => {
	const { data } = await getTableDataApi()
	tableData.list = data // 通过访问list属性重新赋值
}
// 方法四:赋值前再包一层 reactive
const tableData = reactive([])
const getTableData = async () => {
	const { data } = await getTableDataApi()
	tableData = reactive(data) // 赋值前再包一层reactive
}
let data=reactive({
    userData:{}        //里面定义一个对象,这样赋值就不会丢失响应式了。
})
 
//获取接口数据
let res=await getUserApi();  //请求接口
data.userData=res.data;      //将返回的结果赋值给data

对比总结
ref用于定义基本类型和引用类型,reactive仅用于定义引用类型
reactive只能用于定义引用数据类型的原因在于内部是通过ES6的Proxy实现响应式的,而Proxy不适用于基本数据类型
ref定义对象时,底层会通过reactive转换成具有深层次的响应式对象,所以ref本质上是reactive的再封装
在脚本里使用ref定义的数据时,记得加.value后缀
在定义数组时,建议使用ref,从而可避免reactive定义时值修改导致的响应式丢失问题

2、拔高:TS对reactive里对象进行限制

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

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

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

//1.引入刚写好ts类文件
import {dataRule,data} from "@/type/user.ts"
 
//2.重点来了,我实例化出来data,然后用一个变量User接收。
let User=reactive(new data());
 
/*
//实例化出来以后相当于这样的结构:
User={
    userData:{
        name:"",
        age:""
    }
}
*/
 
 
//3.我们调用接口
//获取接口数据
let res=await getUserApi();  //请求接口
User.userData=res.data;      //将返回的结果赋值给data,这样也不会丢失响应式,并且userData也受了TS的限制。

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

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

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

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

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

</script>
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>

4、官方文档对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。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐