reactive 是继 ref 之后最常用的一个响应式 API 了,相对于 ref ,它的局限性在于只适合对象、数组。
TIP
使用 reactive 的好处就是写法跟平时的对象、数组几乎一模一样,但它也带来了一些特殊注意点,请留意赋值部分的特殊说明。
类型声明与定义
reactive 变量的声明方式没有 ref 的变化那么大,基本上和普通变量一样,它的 TS 类型如下:
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
可以看到其用法还是比较简单的,下面是一个 Reactive 对象的声明方式:
//声明对象的类型
interface Member{
id:number
name:string
}
//定义一个对象
const userInfo:Member=reactive({
id:1,
name:'Tom'
})
下面是 Reactive 数组的声明方式:
const uids: number[] = reactive([1, 2, 3])
还可以声明一个 Reactive 对象数组:
// 对象数组也是先声明其中的对象类型
interface Member {
id: number
name: string
}
// 再定义一个为对象数组
const userList: Member[] = reactive([
{
id: 1,
name: 'Tom',
},
{
id: 2,
name: 'Petter',
},
{
id: 3,
name: 'Andy',
},
])
变量的读取与赋值
虽然 reactive API 在使用上没有像 ref API 一样有 .value 的心智负担,但也有一些注意事项要留意。
处理对象
Reactive 对象在读取字段的值,或者修改值的时候,与普通对象是一样的,这部分没有太多问题。
// 声明对象的类型
interface Member {
id: number
name: string
}
// 定义一个对象
const userInfo: Member = reactive({
id: 1,
name: 'Tom',
})
// 读取用户名
console.log(userInfo.name)
// 修改用户名
userInfo.name = 'Petter'
处理数组#
但是对于 Reactive 数组,和普通数组会有一些区别。
普通数组在 “重置” 或者 “修改值” 时都是可以直接操作:
// 定义一个普通数组
let uids: number[] = [1, 2, 3]
// 从另外一个对象数组里提取数据过来
uids = api.data.map((item: any) => item.id)
// 合并另外一个数组
let newUids: number[] = [4, 5, 6]
uids = [...uids, ...newUids]
// 重置数组
uids = []
Vue 2 在操作数组的时候,也可以和普通数组这样处理数据的变化,依然能够保持响应性,但在 Vue 3 ,如果使用 reactive 定义数组,则不能这么处理,必须只使用那些不会改变引用地址的操作。
笔者刚开始接触时,按照原来的思维去处理 reactive 数组,于是遇到了 “数据变了,但模板不会更新的问题” ,如果开发者在学习的过程中也遇到了类似的情况,可以从这里去入手排查问题所在。
举个例子,比如要从服务端 API 接口获取翻页数据时,通常要先重置数组,再异步添加数据,如果使用常规的重置,会导致这个变量失去响应性:
let uids: number[] = reactive([1, 2, 3])
/**
* 不推荐使用这种方式,会丢失响应性
* 异步添加数据后,模板不会响应更新
*/
uids = []
// 异步获取数据后,模板依然是空数组
setTimeout(() => {
uids.push(1)
}, 1000)
要让数据依然保持响应性,则必须在关键操作时,不破坏响应性 API ,以下是推荐的操作方式,通过重置数组的 length 长度来实现数据的重置:
const uids: number[] = reactive([1, 2, 3])
/**
* 推荐使用这种方式,不会破坏响应性
*/
uids.length = 0
// 异步获取数据后,模板可以正确的展示
setTimeout(() => {
uids.push(1)
}, 1000)
特别注意
不要对 Reactive 数据进行 ES6 的解构 操作,因为解构后得到的变量会失去响应性。
比如这些情况,在 2s 后都得不到新的 name 信息:
import { defineComponent, reactive } from 'vue'
interface Member {
id: number
name: string
}
export default defineComponent({
setup() {
// 定义一个带有响应性的对象
const userInfo: Member = reactive({
id: 1,
name: 'Petter',
})
// 在 2s 后更新 `userInfo`
setTimeout(() => {
userInfo.name = 'Tom'
}, 2000)
// 这个变量在 2s 后不会同步更新
const newUserInfo: Member = { ...userInfo }
// 这个变量在 2s 后不会再同步更新
const { name } = userInfo
// 这样 `return` 出去给模板用,在 2s 后也不会同步更新
return {
...userInfo,
}
},
})