Vue 3.x 源码阅读相关笔记 之 reactive 函数
Vue 版本: 3.2.x
总体功能
目前来看reactive 函数主要功能正如官网所说,通过传入的对象,创建一个响应式对象(代理Proxy)。
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers, // 针对COMMON类型
mutableCollectionHandlers, // 针对COLLECTION 类型
reactiveMap
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 只能是对象
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 创建Proxy对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 进行缓存
proxyMap.set(target, proxy)
return proxy
}
详细描述
targetType
先对目标对象进行分类。在代码中,将target对象分为三种类型:COMMON
、COLLECTION
、INVALID
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
针对不同类型,在不同的阶段,进行不同的操作。
Handlers
针对通常情况下的对象(not readonly 、not shallow),创建的Proxy对象,采用如下两种handlers
。
mutable 可变的
mutableHandlers
针对COMMON
类型的对象,该函数对该对象的各个handler进行了定制。
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
接下来,深入到各自的Handler
中进行探究。
- getter
涉及的函数
createGetter
在响应式对象的getter handler中,主要进行依赖的收集。主要代码:
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
const targetIsArray = isArray(target)
// 数组特殊处理
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
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 (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 当属性为对象时,再次执行reactive
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
- 数组的特殊处理
涉及函数
createArrayInstrumentations
数组属于特殊的对象,有自己的特性方法,需要进行特殊处理。
- includes
, indexOf
, lastIndexOf
方法在执行时,会对数组中元素进行访问操作,所以对数组的每个元素进行依赖收集。(只是用于应对可能的响应式需求)
- 'push', 'pop', 'shift', 'unshift', 'splice'
这些方法会改变数组长度length
,而length
是数组的一个属性,也会进行依赖收集,因此在执行这些方法时,要暂停依赖收集,否则会陷入死循环。
/**
* 对数组的 'includes', 'indexOf', 'lastIndexOf' 以及 'push', 'pop', 'shift', 'unshift', 'splice'方法加入追踪,以便进行依赖收集
*
* @returns
*/
function createArrayInstrumentations() {
const instrumentations: Record<string, Function> = {}
// 装配对操作敏感的数组方法,以用于可能的响应式操作
// instrument identity-sensitive Array methods to account for possible reactive
// values
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
// 这里的this是个伪参数,仅用于静态检查,属于ts的用法,参考 this参数:https://www.tslang.cn/docs/handbook/functions.html
instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
// 转换为原始类型
const arr = toRaw(this) as any
// 对数组的每个元素进行追踪,为什么要在这三个方法里进行追踪?因为确实会依赖。依赖追踪是在get方法中进行的
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
// we run the method using the original args first (which may be reactive)
// 如果参数不是响应式的,这一步直接就会有结果
const res = arr[key](...args)
if (res === -1 || res === false) {
// if that didn't work, run it again using raw values.
// 以防参数是响应式的,再执行一次
return arr[key](...args.map(toRaw))
} else {
return res
}
}
})
// 因为length的变化也会被侦听到,所以这些会改变数组长度的方法执行时,就不进行依赖追踪
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
pauseTracking()
// 是因为此处的操作如果追踪的话,会死循环
const res = (toRaw(this) as any)[key].apply(this, args)
resetTracking()
return res
}
})
return instrumentations
}
- setter
涉及函数
createSetter
该handler的主要作用是更新派发(当值发生变化时,通知相应的依赖)
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (!shallow) {
// 转化为原始值
value = toRaw(value)
oldValue = toRaw(oldValue)
// 这一步直接赋值,交给Ref对象内部的响应式机制处理
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
// 只触发当前对象,不管原型链上的target
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
// 如果有这个key,并且发生了改变,才执行trigger
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
- deleteProperty
删除操作会触发执行的handler,拦截删除操作。
该函数同样会执行相应的trigger。
function deleteProperty(target: object, key: string | symbol): boolean {
const hadKey = hasOwn(target, key)
const oldValue = (target as any)[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
- has
该函数是针对 in 操作符的代理方法。
进行了访问操作,则会进行相应的依赖收集。
function has(target: object, key: string | symbol): boolean {
const result = Reflect.has(target, key)
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
}
- ownKeys
用于拦截Reflect.ownKeys方法,但执行Object.keys方法时,也会被拦截(入参为新的代理对象)
这里只干了一件事,就是进行依赖收集。
function ownKeys(target: object): (string | symbol)[] {
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}
mutableCollectionHandlers
针对COLLECTION
类型的对象,创建Proxy对象所使用的Handlers为:
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}
- createInstrumentationGetter()
创建
COLLECTION
类型的getter
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
) => {
// 特定的值直接取
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}
// 如果是集合的其他方法,执行instrumentations中定义的方法
return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
- mutableInstrumentations
将
COLLECTION
类型的各个方法进行重写,以应对可能的响应式需求
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(key)
function get(
target: MapTypes,
key: unknown,
isReadonly = false,
isShallow = false
) {
// #1772: readonly(reactive(Map)) should return readonly + reactive version
// of the value
target = (target as any)[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
// 不管key 和 rawKey是否一样,都能保证被依赖收集
if (key !== rawKey) {
!isReadonly && track(rawTarget, TrackOpTypes.GET, key)
}
!isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
const { has } = getProto(rawTarget)
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
// 进行响应式包装
if (has.call(rawTarget, key)) {
return wrap(target.get(key))
} else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey))
} else if (target !== rawTarget) {
// #3602 readonly(reactive(Map))
// ensure that the nested reactive `Map` can do tracking for itself
// 如果 原始对象上不存在key或者 rawKey ,则直接取值返回。
target.get(key)
}
}
- get 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]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
// 这里的判断方式跟get一致,只是传入的类型由 GET变为 HAS
if (key !== rawKey) {
!isReadonly && track(rawTarget, TrackOpTypes.HAS, key)
}
!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 = toRaw(value)
const target = toRaw(this)
const proto = getProto(target)
const hadKey = proto.has.call(target, value)
if (!hadKey) {
target.add(value)
// 派发ADD类型的变更
trigger(target, TriggerOpTypes.ADD, value, value)
}
return this
}
- set
function set(this: MapTypes, key: unknown, value: unknown) {
value = toRaw(value)
const target = toRaw(this)
const { has, get } = getProto(target)
// 检测key 是否存在于target中
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)
// 更新派发至对应依赖项中
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return this
}
- deleteEntry
function deleteEntry(this: CollectionTypes, key: unknown) {
const target = toRaw(this)
const { has, get } = getProto(target)
let hadKey = has.call(target, key)
// 检测key 是否存在于target中
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
// forward the operation before queueing reactions
// 更新派发至对应依赖项中
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()
if (hadItems) {
trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
}
return result
}
- forEach
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) => {
// important: make sure the callback is
// 1. invoked with the reactive map as `this` and 3rd arg
// 2. the value received should be a corresponding reactive/readonly.
return callback.call(thisArg, wrap(value), wrap(key), observed)
})
}
}
track()
涉及函数track
该函数主要负责依赖收集,每个对象有自己的depsMap ,存储于 targetMap
(全局变量)中,用于存储收集到的依赖。而在depsMap
中,每个属性又有着自己的depsSet
,用来存储各属性收集到的依赖。借助一张图来解释:
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (!isTracking()) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
// TODO 3.2新增,主要服务于effectScope作用域API
trackEffects(dep, eventInfo)
}
trigger()
涉及函数
trigger
和triggerEffects
trigger
:根据对应情况,取出需要触发执行的 ReactiveEffect
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
// 如果是清空操作
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
// 取出depsMap中的所有依赖
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
// 如果变化的是长度
depsMap.forEach((dep, key) => {
// 依赖中大于新值则进行追加
if (key === 'length' || key >= (newValue as number)) {
deps.push(dep)
}
})
} else {
// (稍后运行的相关操作)schedule runs for SET | ADD | DELETE
// key !== undefined
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// (对特定对象的iteration key 也需要进行派发)also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
// 针对非数组对象和Map特殊处理
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
// 这里为什么没对数组处理呢?因为对数组的属性进行删除操作,并不会改变length属性
break
case TriggerOpTypes.SET:
// 针对Map类型特殊处理
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
const eventInfo = __DEV__
? { target, type, key, newValue, oldValue, oldTarget }
: undefined
if (deps.length === 1) {
if (deps[0]) {
if (__DEV__) {
triggerEffects(deps[0], eventInfo)
} else {
triggerEffects(deps[0])
}
}
} else {
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
// dep为数组
effects.push(...dep)
}
}
if (__DEV__) {
triggerEffects(createDep(effects), eventInfo)
} else {
triggerEffects(createDep(effects))
}
}
}
triggerEffects
:将Dep
类型的Set 转化为数组,逐个触发effect对应的回调。
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
}
总结
- 最终目的,就是在取值(get)操作中进行依赖收集,在值有所变化(增、删、改)时,进行更新派发,实现响应式。
其他
- reactive的入参只支持对象(json对象,Array等),不支持原始数据类型(如String、Number、BigInt、undefined、Boolean、Symbol、null等七种。详见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Data_structures)
解释:
由于该函数最终会将传入的参数包装成Proxy
对象,而Proxy对象的入参就要求只能是对象。另一方面,从createReactiveObject
函数中,我们看到target能够支持的类型如下所示:
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
因此,限定reactive
函数的入参只能是对象。
参考
这个版本已经过时了,但仍然具有参考意义
https://zhuanlan.zhihu.com/p/148423054
https://zhuanlan.zhihu.com/p/306540786