Vue3.0Reactivity之ref
前言
这次讲讲ref
这个API,如果没有看过reactive
API的,建议先看下我之前写的关于reactive
的一些理解,对这个ref
的理解会更容易
ref
同样,我们可以在包中找到ref.ts
文件,里面定义了ref
API,
在前面我们翻译ref
API的文章中,我们知道ref
函数其实做的事情和reactive
差不多
区别就是ref
把传进来的对象挂载到它的value
属性上,建立响应式,ref
对于传入的类型基本没有限制,基本类型也可以通过ref
包装成一个响应式对象。
1 | export function ref(value?: unknown) { |
这个API的实现直接返回了createRef
,找到它
1 | function createRef(rawValue: unknown, shallow = false) { |
这个函数的实现不难,我们一段一段看
1 | function createRef(rawValue: unknown, shallow = false) { |
先判断传进来的值,如果是ref对象了,直接返回
这个isRef
的实现其实也非常的简单,如下
1 | export function isRef(r: any): r is Ref { |
也就是判断对象_isRef
属性的值(true
或者false
)来判定。
1 | function createRef(rawValue: unknown, shallow = false) { |
接下来的一行也不难,判断传进来shallow
这个标记(这个标记的意义为否建立浅的响应式)。
如果shallow
为真,直接返回原值,否则调用convert
(意思为转化)函数。
这个函数是什么呢,其实它的实现也很简单。如下
1 | const convert = <T extends unknown>(val: T): T => |
可能在ts的泛型下会觉得有些奇怪,把泛型去掉,再把箭头函数转为function
来定义
1 | function convert(val){ |
是不是感觉非常清晰,判断传进来的值是不是对象,是的话就用reactive
包装这个对象,否则直接返回。
回到之前,
1 | function createRef(rawValue: unknown, shallow = false) { |
这个函数最后构建了一个Ref对象,在Vue的实现中,一个Ref对象即存在._isRef
属性,并且这个属性的值为true
这个对象使用了属性的getter和setter,跟reactive
的做法差不多,get的时候收集依赖,调用track
,set的时候触发依赖,调用trigger
set的实现简单,主要在get的逻辑上存在一些判断
首先判断新值和旧值是不是相等,可能有人疑惑,为啥不直接比较而要用函数呢?
我们可以看下hasChange
函数
1 | export const hasChanged = (value: any, oldValue: any): boolean => |
把泛型去掉,转成function
1 | function hasChanged (value, oldValue) { |
聪明的人应该看出来了,为了判断NaN
这个值,在JavaScript中,NaN
和自己是不相等的。而对于人的逻辑来说,当我多次给响应式对象的一个属性赋NaN
值,我希望它不应该触发依赖,因为我认为NaN === NaN
的。
当新值和旧值都是NaN
,进行比较时,&&
左边会为真,但是&&
右边会为假,总体上就返回了假,也就是值没有改变。
对于传进hasChange
,其中一个使用了toRaw
,这个函数又是什么呢?
1 | export function toRaw<T>(observed: T): T { |
还记得我们写reactive
API时候的那四个WeakMap
吗,也就是尝试对传入对象寻找它的源对象,如果有就返回源对象,如果没有就返回自身。
在git中对于添加这个判断的的提交文本是:ref should not trigger if value did not change
。这里比较的原因就是为了防止赋相同的值重复的触发依赖。
然后把新增赋值给rawValue
,作为下次的判断,然后再判断shallow
(浅响应)来对新的值进行处理。
最后便是trigger
来触发依赖了。
后记
这个ref
如果看过reactive
的那篇文章会更加容易理解。