【vue3源碼】九尼摹、ref源碼解析
參考代碼版本:vue 3.2.37
官方文檔:https://vuejs.org/
ref
接受一個內(nèi)部值跃惫,返回一個響應(yīng)式的应又、可更改的ref
對象石蔗,此對象只有一個指向其內(nèi)部值的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
函數(shù)的返回值罕邀。
createRef
接收兩個參數(shù):rawValue
待轉(zhuǎn)換的值、shallow
淺層響應(yīng)式养距。
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
如果rawValue
本就是ref
類型的會立即返回rawValue
诉探,否則返回一個RefImpl
實例。
RefImpl
class RefImpl<T> {
private _value: T
private _rawValue: T
// 當(dāng)前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
的構(gòu)造器接收兩個值:value
棍厌、__v_isShallow
是否淺層響應(yīng)式肾胯。
constructor(value: T, public readonly __v_isShallow: boolean) {
// 獲取原始值,如果是淺層響應(yīng)式耘纱,原始值就是value敬肚;如果不是淺層響應(yīng)式,原始值是value的原始值
this._rawValue = __v_isShallow ? value : toRaw(value)
// 響應(yīng)式數(shù)據(jù)束析,如果是淺層響應(yīng)式艳馒,是value;否則轉(zhuǎn)為reactive(只有Object類型才會轉(zhuǎn)為reactive)
this._value = __v_isShallow ? value : toReactive(value)
}
當(dāng)獲取new RefImpl()
的value
屬性時员寇,會調(diào)用trackRefValue
進行依賴收集弄慰,并返回this._value
。
export function trackRefValue(ref: RefBase<any>) {
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
中陆爽。
當(dāng)修改new RefImpl()
的value
屬性時,會調(diào)用triggerRefValue
觸發(fā)依賴扳缕。
set value(newVal) {
newVal = this.__v_isShallow ? newVal : toRaw(newVal)
// 當(dāng)newVal與舊原始值不同時墓陈,觸發(fā)依賴
if (hasChanged(newVal, this._rawValue)) {
// 更新原始值及響應(yīng)式數(shù)據(jù)
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
shallowRef
shallowRef
的實現(xiàn)同樣通過createRef
函數(shù),不過參數(shù)shallow
為true
第献。
export function shallowRef(value?: unknown) {
return createRef(value, true)
}
const state = shallowRef({ count: 1 })
effect(() => {
console.log(state.value.count)
})
// 不會觸發(fā)副作用
state.value.count = 2
// 可以觸發(fā)副作用
state.value = {
count: 3
}
為什么state.value.count = 2
不觸發(fā)副作用贡必?
state
初始化時,state._value
就是{ count: 1 }
庸毫,一個普通對象仔拟,當(dāng)使用state.value.count = 2
設(shè)置值時,會先觸發(fā)get
函數(shù)返回state._value
飒赃,然后再修改state._value
利花,因為state._value
是普通對象科侈,所以不會有副作用觸發(fā)。
而當(dāng)使用state.value = { count: 3 }
方式進行修改時炒事,會命中set
函數(shù)臀栈,因為新的值與舊的原始值內(nèi)存地址不同,所以會觸發(fā)副作用挠乳。
triggerRef
強制觸發(fā)ref
的副作用函數(shù)权薯。
export function triggerRef(ref: Ref) {
triggerRefValue(ref, __DEV__ ? ref.value : void 0)
}
實現(xiàn)原理很簡單,就是主動調(diào)用一下triggerRefValue
函數(shù)睡扬。
由于深度響應(yīng)式的ref
會自動進行依賴的觸發(fā)盟蚣,所以triggerRef
主要應(yīng)用于shallowRef
的內(nèi)部值進行深度變更后,主動調(diào)用triggerRef
以觸發(fā)依賴卖怜。例如前面的例子:
const state = shallowRef({ count: 1 })
effect(() => {
console.log(state.value.count)
})
// 不會觸發(fā)副作用
state.value.count = 2
// 主動觸發(fā)副作用
triggerRef(state)
// 可以自動觸發(fā)副作用
state.value = {
count: 3
}
customRef
創(chuàng)建一個自定義的ref
屎开,顯式聲明對其依賴追蹤和更新觸發(fā)的控制方式。
如創(chuàng)建一個防抖ref
马靠,即只在最近一次set
調(diào)用后的一段固定間隔后再調(diào)用:
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
的實現(xiàn):
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
return new CustomRefImpl(factory) as any
}
customRef
返回一個CustomRefImpl
實例奄抽。
class CustomRefImpl<T> {
public dep?: Dep = undefined
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
public readonly __v_isRef = true
constructor(factory: CustomRefFactory<T>) {
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
的實現(xiàn)與RefImpl
的實現(xiàn)差不多,都有個value
的get
甩鳄、set
函數(shù)如孝,只不過get
、set
在內(nèi)部會調(diào)用用戶自己定義的get
與set
函數(shù)娩贷。當(dāng)進行初始化時第晰,會將收集依賴的函數(shù)與觸發(fā)依賴的函數(shù)作為參數(shù)傳遞給factory
,這樣用戶就可以自己控制依賴收集與觸發(fā)的時機彬祖。
總結(jié)
ref
的通過class
實現(xiàn)茁瘦,通過class
的取值函數(shù)和存值函數(shù)進行依賴的收集與觸發(fā)。
對于深度響應(yīng)式的ref
储笑,會在向value
屬性賦值過程中甜熔,將新的值轉(zhuǎn)為reactive
,以達到深度響應(yīng)式的效果突倍。