前景回顾
各位看官,上回书说到,咱们Vue3源码中实现响应式用到了track函数 和trigger函数,两者分别对应着依赖收集和依赖触发。
那么这次我们要了解什么呢?那就继续看看Vue3的田地里面挖出的宝藏源码吧!
额外的知识点,Reflect
来看看下面这个文章吧 ↓↓↓↓↓↓
响应式的触发(Handlers)
警告:以下内容第一次看可能会有点懵,没点代码基础会犯困
baseHandlers主要处理的是数组和对象。 它包含了一些基本的操作,比如获取属性值(get)、设置属性值(set)、删除属性(deleteProperty)等。这些操作都会触发Vue的响应式系统,使得当数据发生变化时,视图也会相应地更新。
collectionHandlers则是用来处理Map、Set、WeakMap、WeakSet等数据结构。 这些数据结构在JavaScript中是非常常用的,所以Vue也提供了对它们的响应式处理。
baseHandlers
要看Vue3对proxy对象进行了什么操作,主要还是看MutableReactiveHandler和BaseReactiveHandler两个类,而MutableReactiveHandler又继承了BaseReactiveHandler。
BaseReactiveHandler
在 Vue3 的源码中,BaseReactiveHandler
是一个非常重要的类,主要作用是拦截对象的属性访问。
关键部分代码
ts
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _shallow = false,
) {}
get(target: Target, key: string | symbol, receiver: object) {
// ...省略部分代码...
const res = Reflect.get(target, key, receiver);
// ...省略部分代码...
if (!isReadonly) {
// 触发相应收集track函数
track(target, TrackOpTypes.GET, key)
}
// ...省略部分代码...
return res;
}
}
get 方法
get
方法用于拦截对象的读取属性操作。它首先检查被访问的属性是否是一些特殊的标志(如 IS_REACTIVE
、IS_READONLY
、IS_SHALLOW
、RAW
),然后根据不同的标志返回不同的值。如果被访问的属性不是这些特殊的标志,那么它会使用 Reflect.get()
来获取属性值,并根据是否只读、是否浅层响应等条件进行额外的处理。
源码解析(可跳过)
源码如下:
ts
// BaseReactiveHandler 是 Vue3 中用于实现响应式系统的基础类
class BaseReactiveHandler implements ProxyHandler<Target> {
// 构造函数接收两个参数,分别表示是否只读和是否浅层响应
constructor(
protected readonly _isReadonly = false,
protected readonly _shallow = false,
) {}
// get 方法用于拦截对象的读取属性操作
get(target: Target, key: string | symbol, receiver: object) {
const isReadonly = this._isReadonly,
shallow = this._shallow
// 如果读取的属性是 IS_REACTIVE,返回 !isReadonly
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
}
// 如果读取的属性是 IS_READONLY,返回 isReadonly
else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
}
// 如果读取的属性是 IS_SHALLOW,返回 shallow
else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
}
// 如果读取的属性是 RAW,并且 receiver 是 reactive 或 readonly 的代理对象,返回 target
else if (key === ReactiveFlags.RAW) {
if (
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target) ||
// receiver 不是 reactive 代理,但具有相同的原型
// 这意味着 receiver 是 reactive 代理的用户代理
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') {
return hasOwnProperty
}
}
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 (shallow) {
return res
}
if (isRef(res)) {
// ref 解包 - 对于 Array + integer key,跳过解包。
return targetIsArray && isIntegerKey(key) ? res : res.value
}
if (isObject(res)) {
// 将返回值也转换为代理。我们在这里进行 isObject 检查
// 以避免无效值警告。此外,需要在此处延迟访问 readonly
// 和 reactive,以避免循环依赖。
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
MutableReactiveHandler
MutableReactiveHandler
用于处理非只读的响应式对象,这个类主要负责拦截对象的属性访问和修改操作,并在必要时触发依赖更新。
关键部分代码
set
方法是 MutableReactiveHandler
的关键部分之一,它用于拦截对象的属性修改操作。当我们试图修改一个响应式对象的属性时,这个方法会被调用。
deleteProperty
方法用于拦截对象的属性删除操作。当我们试图删除一个响应式对象的属性时,这个方法会被调用。
has
和 ownKeys
方法分别用于拦截对象的属性检查操作和获取所有键的操作。
由于代码逻辑有点长,还是详细看看源码吧!
源码解析
以下是带有详细中文注释的 MutableReactiveHandler
类的源码:
ts
// MutableReactiveHandler 类继承自 BaseReactiveHandler
class MutableReactiveHandler extends BaseReactiveHandler {
// 构造函数接收一个参数,表示是否浅层响应
constructor(shallow = false) {
// 调用父类构造函数,设置 _isReadonly 为 false,_shallow 为传入的值
super(false, shallow)
}
// set 方法用于拦截对象的修改属性操作
set(
target: object,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
// 获取旧值
let oldValue = (target as any)[key]
// 如果不是浅层响应
if (!this._shallow) {
// 检查旧值是否只读
const isOldValueReadonly = isReadonly(oldValue)
// 如果新值不是浅层的,且不是只读的,将旧值和新值转换为原始对象
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
// 如果目标不是数组,且旧值是 ref,新值不是 ref
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
// 如果旧值是只读的,返回 false
if (isOldValueReadonly) {
return false
} else {
// 否则,将新值赋给旧值的 value 属性,返回 true
oldValue.value = value
return true
}
}
} else {
// 在浅层模式下,无论对象是否响应式,都直接设置
}
// 检查目标是否已经有该属性
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// 使用 Reflect.set 修改属性值
const result = Reflect.set(target, key, value, receiver)
// 如果目标是原始接收者
if (target === toRaw(receiver)) {
// 如果目标之前没有该属性,触发添加操作的依赖更新
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 如果新值和旧值不同,触发设置操作的依赖更新
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
// 返回 Reflect.set 的结果
return result
}
// deleteProperty 方法用于拦截对象的删除属性操作
deleteProperty(target: object, key: string | symbol): boolean {
// 检查目标是否有该属性
const hadKey = hasOwn(target, key)
// 获取旧值
const oldValue = (target as any)[key]
// 使用 Reflect.deleteProperty 删除属性
const result = Reflect.deleteProperty(target, key)
// 如果删除成功且目标有该属性,触发删除操作的依赖更新
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
// 返回 Reflect.deleteProperty 的结果
return result
}
// has 方法用于拦截对象的属性检查操作
has(target: object, key: string | symbol): boolean {
// 使用 Reflect.has 检查属性
const result = Reflect.has(target, key)
// 如果键不是符号,或者不是内置的符号,进行依赖跟踪
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
// 返回 Reflect.has 的结果
return result
}
// ownKeys 方法用于拦截对象的获取所有键的操作
ownKeys(target: object): (string | symbol)[] {
// 进行依赖跟踪
track(
target,
TrackOpTypes.ITERATE,
isArray(target) ? 'length' : ITERATE_KEY,
)
// 返回 Reflect.ownKeys 的结果
return Reflect.ownKeys(target)
}
}
collectionHandlers
collectionHandlers的代码写法和baseHandlers代码写法有明显差别,在我们收起所有函数内容后,可以看到一系列Set(WeekSet)
和Map(WeekMep)
的方法拦截。
而抛出来的函数主要有以下几个:
- mutableCollectionHandlers 为集合类型创建响应式遍历器。
- shallowCollectionHandlers 为集合类型创建浅层响应式遍历器。
- readonlyCollectionHandlers 为集合类型创建只读响应式遍历器。
- shallowReadonlyCollectionHandlers 为集合类型创建浅层只读响应式遍历器。
通过代码我们发现,他们都调用的createInstrumentationGetter函数,它的主要作用是创建一个用于获取集合元素的函数。
这个函数接收两个参数:isReadonly
(是否是只读响应式)和shallow
(是否是浅层响应式)。根据这两个参数的值,函数会选择对应的处理器(instrumentations
),源码如下:
ts
// 创建处理器
const [
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
shallowReadonlyInstrumentations,
] = /* #__PURE__*/ createInstrumentations()
// 创建一个用于获取集合元素的函数
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
// 根据参数选择对应的处理器
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
// 返回一个 getter 函数
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,
)
}
}
那为什么只处理了一个get函数呢?
因为对于集合类型(如Set
、Map
等),读取操作和修改操作都是通过调用方法进行的,所以只需要捕获其get
方法即可。
例如,我们可以通过map.get(key)
来获取Map
中的元素,通过set.add(value)
来向Set
中添加元素。因此,只需要代理get
方法,就可以对这些操作进行拦截和处理。
那么根据处理器,我们看到了createInstrumentations()
这个函数抛出了4个核心的对象,这4个对象的内部实现就是Vue3中对于集合类型的劫持。
在看源码之前,我们重新认识一下Vue3是如何处理集合类型的响应收集和更新:
- 创建响应式对象 :当我们使用
reactive
或者readonly
函数创建一个Map
对象的响应式版本时,Vue3会使用Proxy
对象对其进行包装。这个Proxy
对象的get
和set
等操作会被重写,以便在执行这些操作时进行额外的处理。 - 劫持集合方法 :在
createInstrumentations
函数中,Vue3为Map
对象的各种方法(如get
、set
、has
、delete
等)创建了对应的处理器(instrumentations
)。这些处理器在执行相应的操作时,会同时触发依赖收集(track
)和派发更新(trigger
)。 - 依赖收集 :当我们在副作用函数(
effect
)中访问Map
对象的某个键时,会触发Proxy
对象的get
操作。在这个操作中,Vue3会调用track
函数进行依赖收集。这意味着,Vue3会记住当前的副作用函数依赖于这个键。 - 派发更新 :当我们修改
Map
对象的某个键时,会触发Proxy
对象的set
操作。在这个操作中,Vue3会调用trigger
函数派发更新。这意味着,Vue3会找出所有依赖于这个键的副作用函数,并重新执行它们,以使它们能够获取到最新的值。
以下源码,请品尝:
createInstrumentations源码(可跳过)
ts
// 创建一组用于处理集合类型的方法
function createInstrumentations() {
// 可变集合的处理器
const mutableInstrumentations: Record<string, Function | number> = {
// 获取集合中的元素
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),
}
// 浅层集合的处理器
const shallowInstrumentations: Record<string, Function | number> = {
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),
}
// 只读集合的处理器
const readonlyInstrumentations: Record<string, Function | number> = {
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),
}
// 浅层只读集合的处理器
const shallowReadonlyInstrumentations: Record<string, Function | number> = {
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),
}
// 需要创建迭代器方法的方法名列表
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,
)
})
// 返回创建好的处理器
return [
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
shallowReadonlyInstrumentations,
]
}
参考文献
- baseHandlers.ts at main · vuejs/core (githu...
- collectionHandlers.ts at main · vuejs/core (githu...
- Vue3.0源码解析「reactive」篇 --- 6.collectionHandler - 掘金 (juejin.cn)
对于一些理解,这篇文章的作者比我写的好:
本章小结
个人觉得这篇文章还是写的比较浅显,很多内容理解的比较生硬。
待俺去再详细看看,继续补充一些例子上来(下次一定)。
结束
响应式的基本概念基本上在这三篇文章中讲了部分,后续作者还会深入学习,持续补充。
谢谢你的观看。