【vue3源碼】八臊诊、reactive——Collection的響應(yīng)式實(shí)現(xiàn)
參考代碼版本:vue 3.2.37
官方文檔:https://vuejs.org/
前文中我們分析了
reactive
對(duì)Object
類型的數(shù)據(jù)處理,這篇文章繼續(xù)介紹對(duì)集合的處理斜脂。
mutableCollectionHandlers
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}
對(duì)于集合抓艳,讀取操作和修改操作都是通過調(diào)用方法(size
除外)進(jìn)行,所以只需要捕獲其get
方法即可帚戳。get
捕獲器通過createInstrumentationGetter
函數(shù)生成玷或。
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
) => {
// 處理特殊的key
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}
return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
createInstrumentationGetter
函數(shù)接收兩個(gè)參數(shù):isReadonly
(是否是只讀響應(yīng)式)儡首、shallow
(是否是淺層響應(yīng)式)。
首先根據(jù)isReadonly
和shallow
的值偏友,獲取對(duì)應(yīng)的instrumentations
蔬胯。
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
instrumentations
是什么呢?
instrumentations
就是個(gè)對(duì)象约谈,它通過createInstrumentations
生成,內(nèi)部重寫了集合的一些方法犁钟。createInstrumentations
方法創(chuàng)建四個(gè)instrumentations
:
-
mutableInstrumentations
:處理可修改的響應(yīng)式集合數(shù)據(jù) -
readonlyInstrumentations
:處理只讀的響應(yīng)式集合數(shù)據(jù) -
shallowInstrumentations
:處理淺層響應(yīng)式集合數(shù)據(jù) -
shallowReadonlyInstrumentations
:處理淺層只讀響應(yīng)式集合數(shù)據(jù)
const [
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
shallowReadonlyInstrumentations
] = /* #__PURE__*/ createInstrumentations()
mutableInstrumentations
const mutableInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key)
},
get size() {
return size(this as unknown as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, false)
}
get
get
函數(shù)可接收四個(gè)參數(shù):target
目標(biāo)集合棱诱、key
、isReadonly
是否是只讀響應(yīng)式涝动、isShallow
是否是淺層響應(yīng)式迈勋。
function get(
target: MapTypes,
key: unknown,
isReadonly = false,
isShallow = false
) {
// readonly(reactive(Map)) 應(yīng)該返回值的readonly + reactive
target = (target as any)[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
// 如果key與其原始對(duì)象不一致,說明key是響應(yīng)式數(shù)據(jù)
if (key !== rawKey) {
// 如果不是只讀的話醋粟,收集key的依賴
!isReadonly && track(rawTarget, TrackOpTypes.GET, key)
}
// 收集key的原始值的依賴
!isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
const { has } = getProto(rawTarget)
// 確定包裝函數(shù)
// 如果是淺層響應(yīng)式靡菇,包裝函數(shù)返回入?yún)ⅲ?value) => value
// 如果是只讀的響應(yīng)式,包裝函數(shù)就是會(huì)將value轉(zhuǎn)為readonly
// 否則使用reactive將value轉(zhuǎn)為reactive
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
// target的原始值自身包含key值
if (has.call(rawTarget, key)) {
return wrap(target.get(key))
} else if (has.call(rawTarget, rawKey)) { // target的原始值自身包含key的原始值
return wrap(target.get(rawKey))
} else if (target !== rawTarget) { // target與原始值不同, 說明target是個(gè)響應(yīng)式數(shù)據(jù)米愿,那么繼續(xù)調(diào)用target.get厦凤。例如readonly(reactive(Map))
target.get(key)
}
}
可以發(fā)現(xiàn),如果通過get方法獲取一個(gè)響應(yīng)式數(shù)據(jù)對(duì)應(yīng)的值時(shí)育苟,會(huì)有兩次依賴的收集较鼓,為什么這么做呢?
其實(shí)這樣做的目的是使通過響應(yīng)式數(shù)據(jù)的原始值設(shè)置Map
時(shí)违柏,能夠照常觸發(fā)依賴博烂。例如下面這個(gè)例子:
const key = ref('a')
const map = reactive(new Map())
map.set(key, 'a')
effect(() => {
console.log(map.get(key))
})
map.set(key, 'b')
map.set(toRaw(key), 'c')
以上代碼會(huì)依次打印a b c
。無論通過key
還是key
的原始值進(jìn)行修改Map
漱竖,都能夠觸發(fā)依賴禽篱。
size
function size(target: IterableCollections, isReadonly = false) {
// 取原始值
target = (target as any)[ReactiveFlags.RAW]
// 收集依賴
!isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
return Reflect.get(target, 'size', target)
}
has
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
const target = (this as any)[ReactiveFlags.RAW]
// 取target/key的原始值
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
// 如果key是響應(yīng)式對(duì)象,收集依賴
if (key !== rawKey) {
!isReadonly && track(rawTarget, TrackOpTypes.HAS, key)
}
// 收集依賴key的原始值對(duì)應(yīng)依賴
!isReadonly && track(rawTarget, TrackOpTypes.HAS, rawKey)
return key === rawKey
? target.has(key)
: target.has(key) || target.has(rawKey)
}
add
function add(this: SetTypes, value: unknown) {
// 獲取value與this的原始對(duì)象
value = toRaw(value)
const target = toRaw(this)
const proto = getProto(target)
const hadKey = proto.has.call(target, value)
// target中不存在value時(shí)馍惹,才能觸發(fā)依賴
if (!hadKey) {
target.add(value)
trigger(target, TriggerOpTypes.ADD, value, value)
}
// 返回this躺率,因?yàn)镾et的add操縱可以鏈?zhǔn)讲僮? return this
}
set
function set(this: MapTypes, key: unknown, value: unknown) {
value = toRaw(value)
const target = toRaw(this)
const { has, get } = getProto(target)
// 先檢查target中是否存在key,無論key值是不是響應(yīng)數(shù)據(jù)
// 如果不存在万矾,再檢查是否存在key的原始數(shù)據(jù)
let hadKey = has.call(target, key)
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
} else if (__DEV__) {
checkIdentityKeys(target, has, key)
}
const oldValue = get.call(target, key)
target.set(key, value)
// 如果不存在key 說明是新增操作肥照,反之為修改操作
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
// 返回this,因?yàn)镸ap的set操縱可以鏈?zhǔn)讲僮? return this
}
為什么兩次檢查key的存在勤众?
保證使用響應(yīng)式數(shù)據(jù)作為key向Map中添加數(shù)據(jù)和使用響應(yīng)式數(shù)據(jù)的原始值作為key向Map中修改數(shù)據(jù)時(shí)舆绎,修改的是同一個(gè)key的數(shù)據(jù)。例如下面這個(gè)例子
const key = reactive({})
const map = reactive(new Map())
map.set(toRaw(key), 'c')
map.set(key, 'b')
console.log(map.size) // 1
delete
function deleteEntry(this: CollectionTypes, key: unknown) {
const target = toRaw(this)
const { has, get } = getProto(target)
let hadKey = has.call(target, key)
// 如果target中沒有key们颜,再尋找是否有key的原始值吕朵,與set相同
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
} else if (__DEV__) {
checkIdentityKeys(target, has, key)
}
const oldValue = get ? get.call(target, key) : undefined
// 進(jìn)行刪除
const result = target.delete(key)
if (hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
clear
function clear(this: IterableCollections) {
const target = toRaw(this)
const hadItems = target.size !== 0
const oldTarget = __DEV__
? isMap(target)
? new Map(target)
: new Set(target)
: undefined
// forward the operation before queueing reactions
const result = target.clear()
// 如果集合中本就沒有值猎醇,clear操作不會(huì)觸發(fā)依賴
if (hadItems) {
trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
}
return result
}
forEach
forEach
函數(shù)由createForEach
創(chuàng)建。createForEach
接收兩個(gè)參數(shù):isReadonly
(是否是只讀響應(yīng)式)努溃、isShallow
(是否是淺層響應(yīng)式)硫嘶。
function createForEach(isReadonly: boolean, isShallow: boolean) {
return function forEach(
this: IterableCollections,
callback: Function,
thisArg?: unknown
) {
const observed = this as any
const target = observed[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
!isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
return target.forEach((value: unknown, key: unknown) => {
// 包裝value及key,使在forEach中訪問到的key與value的響應(yīng)性質(zhì)與this保持一致
return callback.call(thisArg, wrap(value), wrap(key), observed)
})
}
}
shallowInstrumentations
shallowInstrumentations
與shallowInstrumentations
相似梧税,只不過在生成get
和createForEach
函數(shù)時(shí)沦疾,傳遞的參數(shù)不一樣。
const shallowInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, false, true)
},
get size() {
return size(this as unknown as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, true)
}
readonlyInstrumentations
const readonlyInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, true)
},
get size() {
return size(this as unknown as IterableCollections, true)
},
has(this: MapTypes, key: unknown) {
return has.call(this, key, true)
},
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true, false)
}
readonlyInstrumentations
對(duì)象是用來處理只讀響應(yīng)式數(shù)據(jù)的第队,所以所有可修改集合的操作都會(huì)通過操作失敗哮塞。這些可以修改集合的操作函數(shù)都會(huì)被一個(gè)createReadonlyMethod
函數(shù)生成。
createReadonlyMethod
函數(shù)接接收一個(gè)type
參數(shù)凳谦,并返回一個(gè)匿名函數(shù)忆畅。
function createReadonlyMethod(type: TriggerOpTypes): Function {
return function (this: CollectionTypes, ...args: unknown[]) {
if (__DEV__) {
const key = args[0] ? `on key "${args[0]}" ` : ``
console.warn(
`${capitalize(type)} operation ${key}failed: target is readonly.`,
toRaw(this)
)
}
return type === TriggerOpTypes.DELETE ? false : this
}
}
shallowReadonlyInstrumentations
const shallowReadonlyInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, true, true)
},
get size() {
return size(this as unknown as IterableCollections, true)
},
has(this: MapTypes, key: unknown) {
return has.call(this, key, true)
},
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true, true)
}
除了重寫了以上幾個(gè)方法外,還對(duì)keys
尸执、values
等方法也進(jìn)行了重寫:
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
iteratorMethods.forEach(method => {
mutableInstrumentations[method as string] = createIterableMethod(
method,
false,
false
)
readonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
false
)
shallowInstrumentations[method as string] = createIterableMethod(
method,
false,
true
)
shallowReadonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
true
)
})
keys
家凯、values
、entries
如失、Symbol.iterator
的重寫函數(shù)均通過一個(gè)createIterableMethod
函數(shù)生成绊诲。
Symbol.iterator是什么?
集合的Symbol.iterator
函數(shù)可以用來獲取迭代器對(duì)象褪贵,正是因?yàn)榧蠈?shí)現(xiàn)了Symbol.iterator
方法驯镊,所以可以使用for...of
進(jìn)行迭代。而這里需要重寫Symbol.iterator
方法竭鞍,目的是為了實(shí)現(xiàn)使用for...of
迭代代理對(duì)象板惑。如下:
const map = reactive(new Map([['a', 1], ['b', 2]]))
for(const [key, value] of map) {
console.log(key, value)
}
createIterableMethod
接收三個(gè)參數(shù):method
、isReadonly
偎快、isShallow
冯乘。
function createIterableMethod(
method: string | symbol,
isReadonly: boolean,
isShallow: boolean
) {
return function (
this: IterableCollections,
...args: unknown[]
): Iterable & Iterator {
const target = (this as any)[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const targetIsMap = isMap(rawTarget)
// 根據(jù)isPair判斷迭代時(shí)的參數(shù)
const isPair =
method === 'entries' || (method === Symbol.iterator && targetIsMap)
const isKeyOnly = method === 'keys' && targetIsMap
// 獲取迭代器
const innerIterator = target[method](...args)
// 包裝函數(shù)
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
// 依賴收集
!isReadonly &&
track(
rawTarget,
TrackOpTypes.ITERATE,
isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
)
// 返回一個(gè)同時(shí)滿足迭代器協(xié)議和可迭代協(xié)議的對(duì)象
return {
// 迭代器協(xié)議
next() {
// 調(diào)用原始對(duì)象的迭代器的next方法獲取value與done
const { value, done } = innerIterator.next()
return done
? { value, done }
: {
// 包裝key、value
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
done
}
},
// 實(shí)現(xiàn)可迭代協(xié)議晒夹,意味著可以使用for...of迭代map.keys/values/entries()
[Symbol.iterator]() {
return this
}
}
}
}
總結(jié)
reactive
通過Proxy
代理原始對(duì)象裆馒,通過攔截代理對(duì)象的操作進(jìn)行依賴的收集與觸發(fā)。當(dāng)對(duì)代理對(duì)象進(jìn)行讀取操作時(shí)丐怯,進(jìn)行依賴的收集喷好;對(duì)代理對(duì)象進(jìn)行修改操作則觸發(fā)依賴,無論是讀取操作還是修改操作读跷,其實(shí)都是操作的原始對(duì)象梗搅,為了在執(zhí)行修改操作時(shí)不污染原始對(duì)象,都會(huì)先調(diào)用toRaw
獲取value
的原始值,然后再進(jìn)行修改无切。
reactive
的實(shí)現(xiàn)是懶惰的荡短,如果不訪問代理對(duì)象的屬性,那么永遠(yuǎn)不會(huì)將代理對(duì)象的屬性轉(zhuǎn)為代理對(duì)象哆键。
reactive
流程: