Vue3.0Reactivity之ref

前言

这次讲讲ref这个API,如果没有看过reactiveAPI的,建议先看下我之前写的关于reactive的一些理解,对这个ref的理解会更容易

ref

同样,我们可以在包中找到ref.ts文件,里面定义了refAPI,

在前面我们翻译refAPI的文章中,我们知道ref函数其实做的事情和reactive差不多

区别就是ref把传进来的对象挂载到它的value属性上,建立响应式,ref对于传入的类型基本没有限制,基本类型也可以通过ref包装成一个响应式对象。

1
2
3
export function ref(value?: unknown) {
return createRef(value)
}

这个API的实现直接返回了createRef,找到它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function createRef(rawValue: unknown, shallow = false) {
if (isRef(rawValue)) {
return rawValue
}
let value = shallow ? rawValue : convert(rawValue)
const r = {
_isRef: true,
get value() {
track(r, TrackOpTypes.GET, 'value')
return value
},
set value(newVal) {
if (hasChanged(toRaw(newVal), rawValue)) {
rawValue = newVal
value = shallow ? newVal : convert(newVal)
trigger(
r,
TriggerOpTypes.SET,
'value',
__DEV__ ? { newValue: newVal } : void 0
)
}
}
}
return r
}

这个函数的实现不难,我们一段一段看

1
2
3
4
5
6
function createRef(rawValue: unknown, shallow = false) {
if (isRef(rawValue)) {
return rawValue
}
// ...
}

先判断传进来的值,如果是ref对象了,直接返回

这个isRef的实现其实也非常的简单,如下

1
2
3
export function isRef(r: any): r is Ref {
return r ? r._isRef === true : false
}

也就是判断对象_isRef属性的值(true或者false)来判定。

1
2
3
4
5
function createRef(rawValue: unknown, shallow = false) {
// ...
let value = shallow ? rawValue : convert(rawValue)
// ...
}

接下来的一行也不难,判断传进来shallow这个标记(这个标记的意义为否建立浅的响应式)。

如果shallow为真,直接返回原值,否则调用convert(意思为转化)函数。

这个函数是什么呢,其实它的实现也很简单。如下

1
2
const convert = <T extends unknown>(val: T): T =>
isObject(val) ? reactive(val) : val

可能在ts的泛型下会觉得有些奇怪,把泛型去掉,再把箭头函数转为function来定义

1
2
3
function convert(val){
return isObject(val) ? reactive(val) : val
}

是不是感觉非常清晰,判断传进来的值是不是对象,是的话就用reactive包装这个对象,否则直接返回。

回到之前,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function createRef(rawValue: unknown, shallow = false) {
// ...
const r = {
_isRef: true,
get value() {
track(r, TrackOpTypes.GET, 'value')
return value
},
set value(newVal) {
if (hasChanged(toRaw(newVal), rawValue)) {
rawValue = newVal
value = shallow ? newVal : convert(newVal)
trigger(
r,
TriggerOpTypes.SET,
'value',
__DEV__ ? { newValue: newVal } : void 0
)
}
}
}
return r
}

这个函数最后构建了一个Ref对象,在Vue的实现中,一个Ref对象即存在._isRef属性,并且这个属性的值为true

这个对象使用了属性的getter和setter,跟reactive的做法差不多,get的时候收集依赖,调用track,set的时候触发依赖,调用trigger

set的实现简单,主要在get的逻辑上存在一些判断

首先判断新值和旧值是不是相等,可能有人疑惑,为啥不直接比较而要用函数呢?

我们可以看下hasChange函数

1
2
export const hasChanged = (value: any, oldValue: any): boolean =>
value !== oldValue && (value === value || oldValue === oldValue)

把泛型去掉,转成function

1
2
3
function hasChanged (value, oldValue) {
value !== oldValue && (value === value || oldValue === oldValue)
}

聪明的人应该看出来了,为了判断NaN这个值,在JavaScript中,NaN和自己是不相等的。而对于人的逻辑来说,当我多次给响应式对象的一个属性赋NaN值,我希望它不应该触发依赖,因为我认为NaN === NaN的。

当新值和旧值都是NaN,进行比较时,&amp;&amp;左边会为真,但是&amp;&amp;右边会为假,总体上就返回了假,也就是值没有改变。

对于传进hasChange,其中一个使用了toRaw,这个函数又是什么呢?

1
2
3
4
export function toRaw<T>(observed: T): T {
observed = readonlyToRaw.get(observed) || observed
return reactiveToRaw.get(observed) || observed
}

还记得我们写reactiveAPI时候的那四个WeakMap吗,也就是尝试对传入对象寻找它的源对象,如果有就返回源对象,如果没有就返回自身。

在git中对于添加这个判断的的提交文本是:ref should not trigger if value did not change。这里比较的原因就是为了防止赋相同的值重复的触发依赖。

然后把新增赋值给rawValue,作为下次的判断,然后再判断shallow(浅响应)来对新的值进行处理。

最后便是trigger来触发依赖了。

后记

这个ref如果看过reactive的那篇文章会更加容易理解。