【vue3源码】九、ref源码解析
参考代码版本:vue 3.2.37
官方文档:https://vuejs.org/
ref
接受一个内部值,返回一个响应式的、可更改的ref
对象,此对象只有一个指向其内部值的property.value
使用
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
源码解析
export function ref(value?: unknown) {
return createRef(value, false)
}
ref
返回createRef
函数的返回值。
createRef
接收两个参数:rawValue
待转换的值、shallow
浅层响应式。
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
如果rawValue
本就是ref
类型的会立即返回rawValue
,否则返回一个RefImpl
实例。
RefImpl
class RefImpl {
private _value: T
private _rawValue: T
// 当前ref的依赖
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
newVal = this.__v_isShallow ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
RefImpl
的构造器接收两个值:value
、__v_isShallow
是否浅层响应式。
constructor(value: T, public readonly __v_isShallow: boolean) {
// 获取原始值,如果是浅层响应式,原始值就是value;如果不是浅层响应式,原始值是value的原始值
this._rawValue = __v_isShallow ? value : toRaw(value)
// 响应式数据,如果是浅层响应式,是value;否则转为reactive(只有Object类型才会转为reactive)
this._value = __v_isShallow ? value : toReactive(value)
}
当获取new RefImpl()
的value
属性时,会调用trackRefValue
进行依赖收集,并返回this._value
。
export function trackRefValue(ref: RefBase) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
if (__DEV__) {
trackEffects(ref.dep || (ref.dep = createDep()), {
target: ref,
type: TrackOpTypes.GET,
key: 'value'
})
} else {
// 收集依赖到ref.dep中
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
}
与reactive
不同,ref
的依赖会被保存在ref.dep
中。
当修改new RefImpl()
的value
属性时,会调用triggerRefValue
触发依赖。
set value(newVal) {
newVal = this.__v_isShallow ? newVal : toRaw(newVal)
// 当newVal与旧原始值不同时,触发依赖
if (hasChanged(newVal, this._rawValue)) {
// 更新原始值及响应式数据
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
shallowRef
shallowRef
的实现同样通过createRef
函数,不过参数shallow
为true
。
export function shallowRef(value?: unknown) {
return createRef(value, true)
}
const state = shallowRef({ count: 1 })
effect(() => {
console.log(state.value.count)
})
// 不会触发副作用
state.value.count = 2
// 可以触发副作用
state.value = {
count: 3
}
为什么state.value.count = 2
不触发副作用?state
初始化时,state._value
就是{ count: 1 }
,一个普通对象,当使用state.value.count = 2
设置值时,会先触发get
函数返回state._value
,然后再修改state._value
,因为state._value
是普通对象,所以不会有副作用触发。
而当使用state.value = { count: 3 }
方式进行修改时,会命中set
函数,因为新的值与旧的原始值内存地址不同,所以会触发副作用。
triggerRef
强制触发ref
的副作用函数。
export function triggerRef(ref: Ref) {
triggerRefValue(ref, __DEV__ ? ref.value : void 0)
}
实现原理很简单,就是主动调用一下triggerRefValue
函数。
由于深度响应式的ref
会自动进行依赖的触发,所以triggerRef
主要应用于shallowRef
的内部值进行深度变更后,主动调用triggerRef
以触发依赖。例如前面的例子:
const state = shallowRef({ count: 1 })
effect(() => {
console.log(state.value.count)
})
// 不会触发副作用
state.value.count = 2
// 主动触发副作用
triggerRef(state)
// 可以自动触发副作用
state.value = {
count: 3
}
customRef
创建一个自定义的ref
,显式声明对其依赖追踪和更新触发的控制方式。
如创建一个防抖ref
,即只在最近一次set
调用后的一段固定间隔后再调用:
import { customRef } from 'vue'
export 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
的实现:
export function customRef(factory: CustomRefFactory): Ref {
return new CustomRefImpl(factory) as any
}
customRef
返回一个CustomRefImpl
实例。
class CustomRefImpl {
public dep?: Dep = undefined
private readonly _get: ReturnType>['get']
private readonly _set: ReturnType>['set']
public readonly __v_isRef = true
constructor(factory: CustomRefFactory) {
const { get, set } = factory(
() => trackRefValue(this),
() => triggerRefValue(this)
)
this._get = get
this._set = set
}
get value() {
return this._get()
}
set value(newVal) {
this._set(newVal)
}
}
CustomRefImpl
的实现与RefImpl
的实现差不多,都有个value
的get
、set
函数,只不过get
、set
在内部会调用用户自己定义的get
与set
函数。当进行初始化时,会将收集依赖的函数与触发依赖的函数作为参数传递给factory
,这样用户就可以自己控制依赖收集与触发的时机。
总结
ref
的通过class
实现,通过class
的取值函数和存值函数进行依赖的收集与触发。
对于深度响应式的ref
,会在向value
属性赋值过程中,将新的值转为reactive
,以达到深度响应式的效果。