ref
ref最重要的作用,其實是提供了一套Ref類型,什么是ref類型呢?
ref類型
export interface Ref<T = any> { value: T /** * Type differentiator only. * We need this to be in public d.ts but don't want it to show up in IDE * autocomplete, so we use a private Symbol instead. */ // 用此唯一key,來做Ref接口的一個描述符,讓isRef函數做類型判斷 [RefSymbol]: true /** * @internal */ _shallow?: boolean }
對於基本數據類型,函數傳遞或者對象解構時,會丟失原始數據的引用,換言之,我們沒法讓基本數據類型,或者解構后的變量(如果它的值也是基本數據類型的話),成為響應式的數據。
// 我們是永遠沒辦法讓`a`或`x`這樣的基本數據成為響應式的數據的,Proxy也無法劫持基本數據。 const a = 1; const { x: 1 } = { x: 1 }
但是有時候,我們確實就是想一個數字、一個字符串是響應式的,或者就是想利用解構的寫法。那怎么辦呢?只能通過創建一個對象,也即是源碼中的Ref數據,然后將原始數據保存在Ref的屬性value當中,再將它的引用返回給使用者。既然是我們自己創造出來的對象,也就沒必要使用Proxy再做代理了,直接劫持這個value的get/set即可,這就是ref函數與Ref類型的由來。
isRef判斷
如何判斷是否是ref類型,下面是源代碼,還是很簡單的。
// 判斷是否是Ref export function isRef<T>(r: Ref<T> | unknown): r is Ref<T> export function isRef(r: any): r is Ref { return Boolean(r && r.__v_isRef === true) }
創建Ref
那么是怎么創建ref呢?下面我們就講講創建ref。
function createRef(rawValue: unknown, shallow = false) { if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, shallow) }
RefImpl構造函數
這里也定義了get/set,沒有任何Proxy相關的操作。
class RefImpl<T> { private _value: T public readonly __v_isRef = true constructor(private _rawValue: T, public readonly _shallow = false) { // 如果是對象就value就是reactive(val),否則就是自己 this._value = _shallow ? _rawValue : convert(_rawValue) } // 獲取數據 get value() { // 監聽函數收集依賴的方法 track(toRaw(this), TrackOpTypes.GET, 'value') return this._value } // 設置新的值 set value(newVal) { if (hasChanged(toRaw(newVal), this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : convert(newVal) trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal) } } }
convert源碼
const convert = <T extends unknown>(val: T): T => isObject(val) ? reactive(val) : val
這里引入了reactive,如果是對象就value就是reactive(val),否則就是自己,reactive暫時先不講,放后面講
toRef&toRefs
toRef也是創建get/set進行攔截,toRefs就是對數組或者對象的屬性進行遞歸設置get/set。
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly __v_isRef = true
constructor(private readonly _object: T, private readonly _key: K) {}
get value() {
return this._object[this._key]
}
set value(newVal) {
this._object[this._key] = newVal
}
}
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K
): ToRef<T[K]> {
return isRef(object[key])
? object[key]
: (new ObjectRefImpl(object, key) as any)
}
export function toRefs<T extends object>(object: T): ToRefs<T> {
if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`)
}
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}