ref
的实现与reactive
类似,但不同之处在于ref
既可以传入对象也可以传入简单类型的数据,那么vue采取了什么方法监听简单类型数据呢?那么带着这个疑问开始今天的源码学习吧。
构建ref
函数处理复杂数据类型能力
ts
export interface Ref<T = any> {
value: T
}
// 暴露出去的方法,将具体的内部实现交给createRef方法
export function ref(value: unknown) {
return createRef(value, false)
}
// 核心作用:创建一个RefImpl实例
export function createRef(rawValue: unknown, shallow: boolean) {
// 判断传入的值是否经过ref处理过
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
// 判断数据是否已经被处理过,是 RefImpl 实例
export function isRef(r: any): r is Ref {
// 判断传入的值是否存在__v_isRef === true的属性,如果有说明是 RefImpl 实例
return !!(r && r.__v_isRef === true)
}
首先创建用于暴露出去的方法ref
,它只需要返回createRef
函数的返回值即可,将具体实现隐藏在模块内部。
createRef
函数接收两个参数,rawValue
表示ref
函数传入的被监听数据,shallow
是一个布尔值,用于判断是否为简单类型,true
则为简单类型,false
则为复杂类型。
createRef
函数的目的是返回一个RefImpl
实例,为什么要返回这个实例在下文详细解释,总之createRef
函数中通过判断传入的值是否已经经过ref
函数的处理,从而决定是直接返回还是进入生成RefImpl
实例的逻辑。
RefImpl
类
ts
export class RefImpl<T> {
// 私有属性_value存放传入的value值
private _value: T
// 公有属性dep存放依赖关系,默认是undefined
public dep?: Dep = undefined
// 对实例进行标记,表示这个实例已经被ref函数处理过了
public readonly __v_isRef = true
// 构造器
constructor(value: T, public readonly __v_isShallow) {
// 对value进行类型判断,shallow为true表示传入的是简单类型数据,否则是复杂类型数据
this._value = this.__v_isShallow ? value : toReactive(value)
}
// 核心逻辑
get value() {
trackRefValue(this)
return this._value
}
set(neVal) {}
}
// 收集依赖
export function tractValue(ref) {
if (activeEffect) {
return tractEffects(ref.dep || (ref.dep = createDep()))
}
}
生成一个RefImpl
实例需要两个参数value
和shallow
,value
在经过基于shallow
的类型判断后,决定返回处理过的值或未处理的值,在这一部分我们主要关注复杂类型的响应性,所以value
需要通过reactive
函数的处理,返回具有响应性的Proxy
。这也解释了ref
中传入复杂数据的响应性来源,ref
得以处理复杂数据的能力主要是reactive
函数提供的。
toReactive
函数
明确了ref
处理复杂类型数据的能力是来源于reactive
函数,那么就可以在reactive
模块中定义toReactive
函数用于转化数据为proxy
。
ts
// 判断是否为对象
function isObject (value: unknown) {
return value !== null && typeof value === 'object'
}
// 如果是对象则返回经reactive函数处理的proxy,否则直接返回原值
export const toReactive <T extends unknown >(value: T) => {
return isObject(value) ? reactive(value as object) : value
}
核心逻辑
为什么经过ref
处理的数据在访问时需要添加.value
,在这一部分就可以解释了,原因是对数据的访问由RefImpl
实例对象代理了。在ref
中传入的值作为参数被保存在实例对象的私有属性_value
中,.value
的本质就是在调用实例对象的getter
和setter
,在get
操作中无论传入的数据是什么类型都不影响,因此我们先讨论get
的实现。
get
函数被触发时需要做两件事:
- 收集数据对应的执行函数作为依赖。这一步的大部分操作都已经在
reactive
模块中完成,我们只需要调用其中的trackEffects
函数收集存储在dep
属性中的执行函数组成的Set
类型数据,如果dep
属性是undefined
就调用createDep
函数新生成一个。 - 返回私有属性
_value
的值。
总结
到这里关于ref
函数处理复杂数据类型的流程就走完了,可以看出ref
在处理复杂数据类型的能力上很大程度上是依靠reactive
函数提供的,本质上还是通过Proxy
代理对象的属性访问,从而在getter
和setter
中收集依赖和触发依赖。但是ref
函数和reactive
函数还是存在一些不同之处的:
ref
:
ref
函数返回的是RefImpl
实例对象。- 想要访问
ref
处理后的值需要通过.value
,通过实例对象的getter
和setter
得到存储在_value
中的proxy
实例。
reactive
:
reactive
可以直接返回proxy
实例
构建ref
函数处理简单类型数据能力
ts
export class RefImpl<T> {
// 省略
...
// 增加rawValue属性保存传入的原始值,可在set操作时用于比较
private _rawValue: T
constructor(value: T, __v_isShallow: boolean) {
this._value = __v_isShallow ? value : toReactive(value)
// 直接保存原始值
this._rawValue = value
}
// setter
set value(newVal) {
// 判断新旧值是否存在不同
if (hasChange(newVal, this._rawValue)) {
// 先更新原始值
this._rawValue = newVal
// 更新处理后的访问值
this._value = toReactive(newVal)
// 触发依赖
triggerRefValue(this)
}
}
}
// 判断两个值是否不同
const hasChange = (newVal: any, oldVal: any):boolean => {
return !Object.is(newVal, oldVal)
}
// 触发依赖的函数
export function triggerRefValue(ref: any) {
if (ref.dep) {
triggerEffects(ref.dep || (ref.dep = createDep()))
}
}
从这段代码中可以看出RefImpl
实例的setter
是怎么处理数据更新的:在实例对象中使用_rawValue
私有属性存储不加修改的原始值,然后在每次set
操作时根据原始值和新值判断set
操作是否存在数据的更新,同时在数据更新的过程中分开处理更新原始值_rawValue
和实际访问属性_value
的值。最后如果触发dep
属性中保存的依赖。
ref
函数处理简单类型数据不是通过reactive
创建proxy
代理对象,而是生成RefImpl
实例对象,通过get
和set
标记value
方法,让.value
属性被调用的同时主动触发value
方法完成依赖收集 和依赖触发。
总结
ref
函数和reactive
函数都具有给数据添加响应性的能力,但它们在使用和原理上都存在不同。
ref
既可以处理简单数据类型,也可以处理复杂数据类型;reactive
函数只能处理复杂类型数据。ref
函数的本质是生成RefImpl
实例对象,通过get
和set
标记value
方法,达到对value属性的访问拦截的目的;reactive
函数的本质是生成proxy
代理对象,通过代理对象监听源数据。ref
函数的响应性需要通过.value
来保证,而reactive
函数直接操作原来的数据即可。