vue 响应 主要实现逻辑的代码在文件夹packages/reactivity中 代码文件包含如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 . ├── baseHandlers.ts ├── collectionHandlers.ts ├── computed.ts ├── deferredComputed.ts ├── dep.ts ├── effect.ts ├── effectScope.ts ├── index.ts // 导出所有API的文件 ├── operations.ts ├── reactive.ts ├── ref.ts └── warning.ts
整体流程图:
图片来源地址:Vue3 One Piece
这篇将看一下reactive.ts内部实现了哪些方法
reactive Vue3中响应数据核心是 reactive , reactive 中的实现是由 proxy 加 effect 组合,先来看一下 reactive 方法的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 export function reactive<T extends object >(target : T): UnwrapNestedRefs <T>export function reactive (target: object ) { if (isReadonly (target)) { return target } return createReactiveObject ( target, false , mutableHandlers, mutableCollectionHandlers, reactiveMap ) } export function isReadonly (value: unknown ): boolean { return !!(value && (value as Target )[ReactiveFlags .IS_READONLY ]) } export const enum ReactiveFlags { SKIP = '__v_skip' , IS_REACTIVE = '__v_isReactive' , IS_READONLY = '__v_isReadonly' , IS_SHALLOW = '__v_isShallow' , RAW = '__v_raw' }
从代码来看,先判断是否已经代理并标记了只读,如果是直接返回当前。如果没有调用函数createReactiveObject实现对象代理,并返回
createReactiveObject 函数 都在代码里了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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 } if ( target[ReactiveFlags .RAW ] && !(isReadonly && target[ReactiveFlags .IS_REACTIVE ]) ) { return target } const existingProxy = proxyMap.get (target) if (existingProxy) { return existingProxy } const targetType = getTargetType (target) if (targetType === TargetType .INVALID ) { return target } const proxy = new Proxy ( target, targetType === TargetType .COLLECTION ? collectionHandlers : baseHandlers ) proxyMap.set (target, proxy) return proxy } 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 } } function getTargetType (value: Target ) { return value[ReactiveFlags .SKIP ] || !Object .isExtensible (value) ? TargetType .INVALID : targetTypeMap (toRawType (value)) }
阅读代码后,可以确定在排除各种边界情况后。所有的后续操作都指向了代理的钩子处理函数上。同时不同的数据类型具有不同的钩子函数
Object和Array使用的钩子函数是baseHandlers
Map, Set, WeakMap和WeakSet, 使用的钩子函数是collectionHandlers
但是这两个个只是形参的名字,实际入参需要看调用的位置具体的后续钩子函数的定义会在后面继续深入谈。 这里只查看reactive的文件实现的逻辑
readonly 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export function readonly <T extends object >( target : T ): DeepReadonly <UnwrapNestedRefs <T>> { return createReactiveObject ( target, true , readonlyHandlers, readonlyCollectionHandlers, readonlyMap ) }
从代码来看与reactive方法一样,都是调用的createReactiveObject函数,只是入参不一致 但是从代码来看,判断是否是只读是通过 target[ReactiveFlags.RAW]是否包含这个属性来决定的, 但是在createReactiveObject函数只是判断了是否是只读,是的话直接返回。并没有看到添加这个标记的位置。这里直接剧透
它是在readonlyHandlers的get钩子中加入的详细代码如下()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function createGetter (isReadonly = false , shallow = false ) { return function get (target: Target, key: string | symbol, receiver: object ) { if (key === ReactiveFlags .IS_REACTIVE ) { return !isReadonly } else if (key === ReactiveFlags .IS_READONLY ) { return isReadonly } else if (key === ReactiveFlags .IS_SHALLOW ) { return shallow } } }
shallowReactive
和 reactive() 不同,这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。
1 2 3 4 5 6 7 8 9 10 11 12 13 export function shallowReactive<T extends object >( target : T ): ShallowReactive <T> { return createReactiveObject ( target, false , shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap ) }
shallowReadonly
和 readonly() 不同,这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。
1 2 3 4 5 6 7 8 9 export function shallowReadonly<T extends object >(target : T): Readonly <T> { return createReactiveObject ( target, true , shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap ) }
toRaw
根据一个 Vue 创建的代理返回其原始对象。
1 2 3 4 5 6 export function toRaw<T>(observed : T): T { const raw = observed && (observed as Target )[ReactiveFlags .RAW ] return raw ? toRaw (raw) : observed }
从代码来看,只是取值,然后判断是否是真在绝对是否是递归调用还是直接返回值。
但是有取值,那么真正逻辑就在handers中的get了,下面是相关代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function createGetter (isReadonly = false , shallow = false ) { return function get (target: Target, key: string | symbol, receiver: object ) { else if ( key === ReactiveFlags .RAW && receiver === (isReadonly ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap ).get (target) ) { return target } } }
从代码来看就是匹配到ReactiveFlags.RAW这个key以后,根据target去不同的WeakMap中获取有没有创建的代理,在和当前的代理进行判断,是不是同一个代理,如果是的话直接返回target
这里有一个疑问,为什么不直接返回代理记录的target呢?要多这么一步
markRaw
将一个对象标记为不可被转为代理。返回该对象本身。
官网文档
1 2 3 4 5 6 7 8 9 10 11 12 export function markRaw<T extends object >(value : T): Raw <T> { def (value, ReactiveFlags .SKIP , true ) return value } export const def = (obj: object , key: string | symbol, value: any ) => { Object .defineProperty (obj, key, { configurable : true , enumerable : false , value }) }
这里就是在当前对象上加入了特殊的属性,后续数据处理时跟着特殊属性特殊处理
is开头的函数和to开头的函数 好了到这里为止,基本已经看完了reactive.ts中主要的函数以及代码实现,还有一些is开头进行判断对象属性的函数没有详细查看下面仅列出,基本都是返回对应的标记枚举,真正判断与返回逻辑都在get钩子函数中实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 export function isReactive (value: unknown ): boolean { if (isReadonly (value)) { return isReactive ((value as Target )[ReactiveFlags .RAW ]) } return !!(value && (value as Target )[ReactiveFlags .IS_REACTIVE ]) } export function isReadonly (value: unknown ): boolean { return !!(value && (value as Target )[ReactiveFlags .IS_READONLY ]) } export function isShallow (value: unknown ): boolean { return !!(value && (value as Target )[ReactiveFlags .IS_SHALLOW ]) } export function isProxy (value: unknown ): boolean { return isReactive (value) || isReadonly (value) } export const toReactive = <T extends unknown>(value : T): T => isObject (value) ? reactive (value) : value export const toReadonly = <T extends unknown>(value : T): T => isObject (value) ? readonly (value as Record <any , any >) : value
结尾 从目前的代码来看我们可以得出几个结论
reactive只支持引用类型的数据
该文件中主要是各种API的封装具体的逻辑分散在各个API对应的handlers中
对于不同的数据类型,大体上分为两种handlers,即baseHandlers.ts和collectionHandlers.ts
下面继续按照源代码文件探索 baseHandlers.ts