Vue3中的ref和reactive
使用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。
更多推荐
所有评论(0)