[vue3] Vue3源碼閱讀筆記 reactivity - baseHandlers
源碼位置:https://github.com/vuejs/core/blob/main/packages/reactivity/src/baseHandlers.ts
baseHandler用于處理對象、數組類型的getter和setter。
這個文件中主要有兩個函數,和三個類。
arrayInstrucmentations和hasOwnProperty這兩個函數主要起到輔助作用;
3個類:
BaseReactiveHandler:負責處理getter;MutableReactiveHandler和ReadonlyReactiveHandler:負責處理setter;
依賴
依賴比較零碎,大致分為以下幾種:
- 用于判斷數據類型;
Vue內置的Flag或Type,用來標記對象或者操作的類型;- 暫停與重置依賴追蹤和任務調度的方法;
- 其它零碎的比如:
warning:在控制臺輸出警告信息;makeMap:傳入一個用,分隔的多個key組成的字符串,返回一個has函數用于檢查后續傳入的key是否存在于一開始傳入的字符串中。
arrayInstrucmentations
arrayInstrumentations這個對象用于記錄一些處理過的數組方法(攔截操作),通過createArrayInstrumentations構建后大概長這樣:
{
...
'push': function(this, ...args){...},
'indexOf': function(this, ...args){...},
...
}
攔截這些方法的原因:
-
['includes', 'indexOf', 'lastIndexOf']:數組中可能包含響應式對象,這幾個方法是需要比較數組元素的,直接比較可能會出錯,因此需要攔截這些方法,在比較的過程中考慮使用toRaw轉成原始對象進行比較。 -
['push', 'pop', 'shift', 'unshift', 'splice']:這些方法會改變數組的長度length屬性,從而觸發與length屬性相關的effect,而effect中如果又有這些方法,那么就會導致死循環。因此,這些方法需要被攔截做特殊處理,在執行這些方法的時候要暫停依賴的追蹤和調度。
源碼與注釋:
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()
function createArrayInstrumentations() {
const instrumentations: Record<string, Function> = {}
// 給需要處理可能包含響應式值的數組方法增加攔截邏輯
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
// 將 this 轉換為原始數組
const arr = toRaw(this) as any
// 追蹤數組每個元素的 GET 操作
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
// 先使用原始參數(可能是響應式的)運行原方法
const res = arr[key](...args)
// 如果結果是 -1 或 false,說明沒有找到或不匹配,再次使用原始值運行一次
if (res === -1 || res === false) {
return arr[key](...args.map(toRaw))
} else {
return res
}
}
})
// 攔截會改變數組長度的方法,避免長度變化被追蹤,從而防止出現無限循環的問題 (#2137)
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
// 暫停追蹤
pauseTracking()
// 暫停調度
pauseScheduling()
// 使用原始數組運行原方法
const res = (toRaw(this) as any)[key].apply(this, args)
// 重置調度
resetScheduling()
// 重置追蹤
resetTracking()
return res
}
})
return instrumentations
}
hasOwnProperty
Vue對hasOwnProperty做了特殊處理,主要在于處理以下問題:
- 確保需要檢查的
key只能是Symbol類型或string類型,如果是其它,則通過String()轉為字符串; - 使用
toRaw在原始對象上查詢key是否存在; - 使用
track追蹤key;
源碼:
function hasOwnProperty(this: object, key: unknown) {
// #10455 hasOwnProperty may be called with non-string values
if (!isSymbol(key)) key = String(key)
const obj = toRaw(this)
track(obj, TrackOpTypes.HAS, key)
return obj.hasOwnProperty(key as string)
}
BaseReactiveHandler
這個類主要負責配置getter,當reactiveAPI包裝的響應式對象的某個key被讀取時,會觸發這里的getter:
- 如果讀取的
key是內置的ReactiveFlags,返回相應的值; - 如果
target是一個數組,那么需要應用上述arrayInstrucmentations記錄的處理過的數組; - 如果
key是hasOwnProperty,返回上述特殊處理過的hasOwnProperty; - 對
key記錄依賴。
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false, // 是否只讀
protected readonly _isShallow = false, // 是否淺層響應式
) {}
get(target: Target, key: string | symbol, receiver: object) {
const isReadonly = this._isReadonly,
isShallow = this._isShallow
// 處理 ReactiveFlags 特殊標志
if (key === ReactiveFlags.IS_REACTIVE) {
// 判斷目標是否是響應式的
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
// 判斷目標是否是只讀的
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
// 判斷目標是否是淺層響應的
return isShallow
} else if (key === ReactiveFlags.RAW) {
// 處理 RAW 標志
if (
receiver ===
(isReadonly
? isShallow
? shallowReadonlyMap
: readonlyMap
: isShallow
? shallowReactiveMap
: reactiveMap
).get(target) ||
// receiver 不是響應式代理,但具有相同的原型
// 這意味著 receiver 是響應式代理的用戶代理
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) {
return target
}
// 提前返回 undefined
return
}
const targetIsArray = isArray(target)
// 處理非只讀的情況
if (!isReadonly) {
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
// 對于數組的特殊方法,使用上述的方法攔截處理
return Reflect.get(arrayInstrumentations, key, receiver)
}
if (key === 'hasOwnProperty') {
// 特殊處理 hasOwnProperty 方法
return hasOwnProperty
}
}
// 默認的 Reflect.get 操作
const res = Reflect.get(target, key, receiver)
// 處理內置符號和非可追蹤的鍵
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 處理非只讀情況,執行追蹤操作
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 處理淺層響應情況
if (isShallow) {
return res
}
// 處理 Ref 類型的值
if (isRef(res)) {
// 如果是數組并且鍵是整數,則跳過 unwrap
return targetIsArray && isIntegerKey(key) ? res : res.value
}
// 處理對象類型的值,將返回的值轉化為代理對象
if (isObject(res)) {
// 將返回的值轉換為代理對象,避免循環依賴
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
MutableReactiveHandler
這個類實現了對set、deleteProperty、has、ownKeys的攔截
class MutableReactiveHandler extends BaseReactiveHandler {
constructor(isShallow = false) {
super(false, isShallow) // 調用父類構造函數,設置只讀標志為 false
}
set(
target: object,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
let oldValue = (target as any)[key] // 獲取目標對象中原有的值
if (!this._isShallow) {
// 如果不是淺層響應,進行深層處理
const isOldValueReadonly = isReadonly(oldValue) // 判斷原有值是否是只讀的
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue) // 獲取原有值的原始對象
value = toRaw(value) // 獲取新值的原始對象
}
// 如果原有值是 ref 類型并且新值不是 ref 類型
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
// 如果原有值是只讀的,返回 false
return false
} else {
oldValue.value = value // 更新 ref 的值
return true
}
}
} else {
// 在淺層模式中,直接設置對象,不考慮其是否為響應式
}
// 判斷目標對象是否之前已經有這個鍵
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver) // 使用 Reflect 設置值
// 判斷target是否是實際被修改的對象
if (target === toRaw(receiver)) {
if (!hadKey) {
// 如果之前沒有這個鍵,觸發 ADD 操作
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 如果鍵已經存在且新值不同,觸發 SET 操作
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
deleteProperty(target: object, key: string | symbol): boolean {
const hadKey = hasOwn(target, key) // 判斷目標對象是否有這個鍵
const oldValue = (target as any)[key] // 獲取原有值
const result = Reflect.deleteProperty(target, key) // 使用 Reflect 刪除屬性
if (result && hadKey) {
// 如果刪除成功且目標對象之前有這個鍵,觸發 DELETE 操作
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
has(target: object, key: string | symbol): boolean {
const result = Reflect.has(target, key) // 使用 Reflect 檢查是否存在該鍵
if (!isSymbol(key) || !builtInSymbols.has(key)) {
// 如果鍵不是內置符號,追蹤 HAS 操作
track(target, TrackOpTypes.HAS, key)
}
return result
}
ownKeys(target: object): (string | symbol)[] {
// 追蹤 ITERATE 操作,用于獲取對象的所有鍵
track(
target,
TrackOpTypes.ITERATE,
isArray(target) ? 'length' : ITERATE_KEY,
)
return Reflect.ownKeys(target) // 使用 Reflect 獲取對象的所有鍵
}
}
解析:set里的trigger時機:
// 判斷target是否是實際被修改的對象
if (target === toRaw(receiver)) {
if (!hadKey) {
// 如果之前沒有這個鍵,觸發 ADD 操作
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 如果鍵已經存在且新值不同,觸發 SET 操作
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
如果target !== toRaw(receiver)說明此時不是在對象上修改它本身的屬性,而是通過原型鏈上的其它對象。這種情況不會觸發更新。
ReadonlyReactiveHandler
這個類比較簡單,主要是攔截set和deleteProperty這兩個會改變對象的操作。
開發模式下會在控制臺輸出警告。
注意set和deleteProperty操作會返回true,這是為了符合Proxy規范:即使某些操作被攔截并不實際改變對象的狀態,仍然需要返回一個布爾值以指示操作的成功或失敗。
class ReadonlyReactiveHandler extends BaseReactiveHandler {
constructor(isShallow = false) {
super(true, isShallow) // 調用父類構造函數,將 isReadonly 設置為 true,表示對象是只讀的
}
set(target: object, key: string | symbol, value: unknown): boolean {
if (__DEV__) {
warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target,
)
}
return true // 返回 true,表示設置操作被忽略
}
deleteProperty(target: object, key: string | symbol): boolean {
if (__DEV__) {
warn(
`Delete operation on key "${String(key)}" failed: target is readonly.`,
target,
)
}
return true // 返回 true,表示刪除操作被忽略
}
}

浙公網安備 33010602011771號