Vue3.0 常用响应式API的使用和原理分析(二)
reactive
对传入的类型是有限制的,必须是对象或者数组。对一些基础类型,例如string
, number
,boolean
等不支持,如果要使用reactive
API必须将这些基础类型封装成对象,这样显然是不太科学的。因此Vue 3.0
提供了ref
API。
Ref
是一个接口
, 它最主要的是有一个value
属性可以获取值和赋值。
export interface Ref<T = any> {
value: T
_shallow?: boolean
}
ref
使用场景
ref使用将数据变为响应式数据。
代码解释:
- 通过
ref
将字符串变为了一个响应式对象person
;- 通过
person.value
给person
进行新值的设置,也是通过person.value
获取响应式对象person
的值。
实现原理
-
createRef
传入的参数如果已经是ref
对象,就直接返回;如果不是就利用RefImpl
进行封装。
-
RefImpl
有两个私有变量_value
和_rawValue
,_rawValue
是原始值,_value
是操作的值。- 如果
value
值是原始数据,_value
和_rawValue
都等于value
; - 如果
value
值是数组或者对象,_value
被转换成了reactive
响应式对象,_rawValue
就是响应式对象的原始对象; -
get
函数先收集依赖,然后返回_value
是操作的值; -
set
函数先比对原始值有没有变化,如果变化了就设置_value
和_rawValue
,然后分发依赖。
- 如果
ref
和reactive
相关的一些疑问?
问题1:基础类型数据变为响应式对象用
ref
API,对象或者数组变为响应式对象用reactive
API?答案1:一般是这样使用的,但是
ref
也是可以将对象或者数组变为响应式对象的,因为其内部实现机制也是基于reactive
。
问题2:既然
ref
包含了reactive
的功能,为什么不只提供ref
API就一切都搞定了。答案2:
ref
的一个特点是提供了set
方法, 可以将整个原值数据value
完全替换掉,类似于let
,而reactive
是不能这样操作的,只能对原值数据value
的属性进行修改, 类似于const
的限制。即
ref
类似于let
,reactive
类似于const
,他们的作用场景不一样。
shallowRef
使用场景
只需要监测对象的替换,不需要监测对象的属性修改。原始数据类型
shallowRef
和ref
的效果没有差别。
const p1 = {name: "hehe"};
const p2 = {name: "xixi"};
// 响应式数据
const person = shallowRef(p1);
person.value.name = "haha"; // 不会监测到数据变化
person.value = p2; // 会监测到数据变化
reactive
不存在替换对象的情况,所以shallowReactive
是能监测到外部属性的变化,不能监测到内部属性的变化。
实现原理
-
shallowRef
不会将对象转换成reactive
对象,只有value
值变化后才会分发依赖。
你可能会好奇,
person.value.name = "haha"
设置新值后hasChanged(newVal, this._rawValue)
不是应该true
分发依赖吗?其实
person.value.name = "haha"
这里调用的是get
方法,调用的是get
方法,调用的是get
方法。和set
方法没有关系哦~~~什么时候调用
set
方法?当然是person.value = p2;
这个方法啦。 希望没有被绕晕啊~~~
isRef
使用场景
判断一个对象是否是
ref
对象
实现原理
-
isRef
很简单,就是判断__v_isRef
是否为true
。因为RefImpl
的__v_isRef
就是true
。
unref
使用场景
获取
ref
对象的_value
值,有可能是reactive
对象(因为不是获取_rawValue
的值)。
实现原理
unreftoRef
使用场景
将
reactive
响应式对象的某个属性创建一个ref
对象,方便赋值和取值。
const zhangshanfeng = reactive({
name: '张三丰',
age: 100,
child: {
name: '张翠山',
age: 40,
child: {
name: '张无忌',
age: 20
}
}
})
const wuji = toRef(zhangshanfeng.child, 'child'); // 获得张无忌的ref对象
wuji.value.age += 10; // 修改张无忌的年龄
这个API的主要功能是当只需要操作响应式数据的部分数据时,将部分数据提取成为一个
ref
对象,然后方便操作。例子中如果要操作张无忌的年龄得使用zhangshanfeng.child.child.age += 10
,比较繁琐。这个接口也比较适合网络请求的返回值的处理,可能在某些请求中只有一部分数据是需要展示的,这部分提取出来处理就行了。
实现原理
- 用
ObjectRefImpl
处理object
和key
;
-
get
就是取object
的key
属性的值,set
就是设置object
的key
属性的值。由于object
是响应式对象,所以其实调用的就是响应式对象的get
和set
方法。
toRefs
使用场景
将
reactive
响应式对象的每个属性创建一个ref
对象,方便赋值和取值。
const zhangshanfeng = reactive({
name: '张三丰',
age: 100,
child: {
name: '张翠山',
age: 40,
child: {
name: '张无忌',
age: 20
}
}
})
const refs = toRefs(zhangshanfeng); // 获得张三丰的所以属性的refs。
// 结果
{
name: <ObjectRefImpl>{_object: zhangsanfeng, key: "name"},
age: <ObjectRefImpl>{_object: zhangsanfeng, key: "age"},
child: <ObjectRefImpl{_object: zhangsanfeng, key: "child"},
}
实现原理
- 就是对每个属性分别执行
toRef
调用
customRef
自定义一个
ref
对象,实现自己的功能。
下面官方给的一个防抖的例子:get
方法就是返回值,set
方法是延迟200毫秒才设置值,在这200毫秒如果设置了新值,就重新计时200毫秒再赋值。
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
实现原理
-
customRef
的参数track
和trigger
分别是() => trackRefValue(this)
和() => triggerRefValue(this)
,可以收集依赖和分发依赖,customRef
持有返回的对象的get
和set
方法,这两个方法就是真正执行的赋值和取值的方法。