前言
本文是关于Vue3源码的reactive响应式模块重点分析, 自己看了源码,运行测试用例,同时参考了当前网上的一些优秀的源码分析,在此感谢,很有帮助;关于本文,如有不妥之处,欢迎之处~
Vue3源码模块化分
- runtime-core、runtime-dom、runtime-test这三个文件夹都是Vue 运行时相关的核心代码。
- compiler-core、compiler-dom、compiler-sfc这三个文件夹是 Vue 实现的编译器相关代码。
- server-renderer 是服务端渲染部分。
- vue 文件夹是整个项目打包构建之后的出口文件。
- reactive 文件夹是响应式系统部分
本文只分析
reactivity 模块
整体框架流程图
注:
- isObservableType:
Object, Array, Set, Map, WeakMap, WeakSet 几种类型 - 基本类型:
string, number, boolean 及isObservable 等 - 不同颜色标识对应的阶段, 在后文将分模块详细总结
ref.ts
主要逻辑
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 | const isRefSymbol = Symbol() // 生成一个唯一key export interface Ref<T = any> { [isRefSymbol]: true // 用此唯一key,来做Ref接口的一个描述符,让isRef函数做类型判断 value: UnwrapRef<T> // Ref类型的值是UnwrapRef类型,下面有定义 } // 通过判断val 是否为对象(非null), 来决定是否采用reactive进行Proxy代理 const convert = <T extends unknown>(val: T): T => isObject(val) ? reactive(val) : val } // 根据_isRef判断是否是Ref类型 export function isRef(r: any): r is Ref { return r ? r._isRef === true : false } // 重载 export function ref<T extends Ref>(raw: T): T export function ref<T>(raw: T): Ref<T> export function ref<T = any>(): Ref<T> export function ref(raw?: unknown) { // 非Ref直接返回,不处理 if (isRef(raw)) { return raw } // 是Ref类型,进行转换, 即只有是Ref类型同时非null的对象需要进行代理 raw = convert(raw) const r = { _isRef: true, // get 和 set处理与Proxy类似,都是依赖追踪和响应依赖 get value() { // 访问属性时, 依赖追踪ref类型的value属性, 并返回经过转换后的值 track(r, TrackOpTypes.GET, 'value') return raw }, set value(newVal) { // 更改属性值时, 触发响应, 并对新值进行转换处理 raw = convert(newVal) trigger( r, TriggerOpTypes.SET, 'value', __DEV__ ? { newValue: newVal } : void 0 ) } } // 最后返回组装后的Ref return r } |
Ref函数可以处理任何类型, 将其转化为响应式对象, 但一般用来处理基本类型(string/number/boolean等)
测试:
1 2 3 4 5 6 | setup() { const state = ref(0) // value => ref console.log(state.value) // 0 state.value = 1 console.log(state.value) // 1 } |
上面的例子, ref函数的参数是数字类型0, 通过添加getter和setter方法, 使其成为响应式数据, 最后经过处理后成为Ref类型(包含_isRef, value属性)并返回. 当访问.value属性时触发getter, 同时进行依赖追踪track; 当改变value属性值时, 重新对新值进行上一步转换处理
UnwrapRef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | type UnwrapArray<T> = { [P in keyof T]: UnwrapRef<T[P]> } // Recursively unwraps nested value bindings. // 根据泛型T类型递归地展开(object, Array, ComputedRef, Ref)嵌套值并绑定, 主要作用是在以下嵌套情况中,关于ref类型的值,不用使用`.value`访问, 直接解构 // 以下类型声明是访问对象属性obj['prop'], 返回属性值 export type UnwrapRef<T> = { cRef: T extends ComputedRef<infer V> ? UnwrapRef<V> : T ref: T extends Ref<infer V> ? UnwrapRef<V> : T array: T extends Array<infer V> ? Array<UnwrapRef<V>> & UnwrapArray<T> : T object: { [K in keyof T]: UnwrapRef<T[K]> } }[T extends ComputedRef<any> ? 'cRef' : T extends Ref ? 'ref' : T extends Array<any> ? 'array' : T extends Function | CollectionTypes ? 'ref' // bail out on types that shouldn't be unwrapped : T extends object ? 'object' : 'ref'] |
UnwrapRef作为类型, 在reactive模块中, 主要用来约束代理后的数据, 自动解嵌套, 涉及到的地方有以下两处:
第一是在reactive.ts中用来约束定义reactive返回值
1 2 3 | // reactive.ts type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T> export function reactive(T = any): UnwrapNestedRefs<T> |
第二是在ref.ts中, 用来约束ref函数的返回值的value属性
1 2 3 4 5 | // ref.ts export interface Ref<T = any> { [isRefSymbol]: true value: UnwrapRef<T> } |
综合一起举个栗子:
1 2 3 4 5 6 | // reactive<Ref<{[key]: Ref}>> setup() { const r1 = ref({a: ref(0)}) const r = reactive(r1) console.log(r.value.a) // 0, 此处直接解嵌套, 不用r.value.a.value } |
上面的栗子, 结构是
以下是源码ref.spec.ts文件关于此块的相关测试用例:
个人觉得(实际不清楚), Vue3这样设计,是为了简化结构,类似ES6解构的思想, 在嵌套的结构中, 自动展开嵌套的值或Ref
具体可查看源码的ref.spec.ts文件
toRefs
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 | export function toRefs<T extends object>( object: T ): { [K in keyof T]: Ref<T[K]> } { if (__DEV__ && !isReactive(object)) { console.warn(`toRefs() expects a reactive object but received a plain one.`) } const ret: any = {} // 遍历对象的所有key,将其值转化为Ref数据 for (const key in object) { ret[key] = toProxyRef(object, key) } return ret } function toProxyRef<T extends object, K extends keyof T>( object: T, key: K ): Ref<T[K]> { return { _isRef: true, get value(): any { return object[key] }, set value(newVal) { object[key] = newVal } } as any } |
通过
toRefs 解决的是对象的解构丢失原始数据引用的问题, 开发者在函数中错误的解构 reactive,来返回基本类型。
reactive.ts
reactive: 本库的核心方法,传递一个object类型的原始数据,通过Proxy,返回一个代理数据。在这过程中,劫持了原始数据的任何读写操作。进而实现访问或改变代理数据时,能触发依赖其的监听函数effect。
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | // WeakMaps that store {raw <-> observed} pairs. const rawToReactive = new WeakMap<any, any>() // 原始数据 和 响应式数据的映射 const reactiveToRaw = new WeakMap<any, any>() // 响应式数据 和 原始数据的映射 const rawToReadonly = new WeakMap<any, any>() // 原始数据 和 只读的映射 const readonlyToRaw = new WeakMap<any, any>() // 只读数据 和 原始数据的映射 // WeakSets for values that are marked readonly or non-reactive during // observable creation. const readonlyValues = new WeakSet<any>() const nonReactiveValues = new WeakSet<any>() // nonReactiveValues 存储非响应式对象, 如: DOM const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet]) const isObservableType = /*#__PURE__*/ makeMap( 'Object,Array,Map,Set,WeakMap,WeakSet' ) // 可以被观察的值同时具备的条件: // 非Vue对象 && 非虚拟节点 && 在可被观察的类型(Object,Array,Map,Set,WeakMap,WeakSet)中 && 不是非响应式 // toRawType: 获取原生的数据类型 // makeMap: 过滤类型, 返回筛选函数 const canObserve = (value: any): boolean => { return ( !value._isVue && !value._isVNode && isObservableType(toRawType(value)) && !nonReactiveValues.has(value) ) } // only unwrap nested ref 只展开嵌套ref type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T> export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. // 若target在只读=>原生数据映射中, 直接返回 if (readonlyToRaw.has(target)) { return target } // target is explicitly marked as readonly by user // target 被用户标记为只读, 按只读类别处理 if (readonlyValues.has(target)) { return readonly(target) } // 调用createReactiveObject()函数创建响应式对象 return createReactiveObject( target, // 需要被代理的目标对象 rawToReactive, // 原生=>响应式数据的映射(weakMap) reactiveToRaw, // 响应式=>原生数据的映射(weakMap) mutableHandlers, // 可变数据(Object,Array)的处理回调 mutableCollectionHandlers // 可变集合(Map,Set,WeakMap,WeakSet)的处理回调 ) } // 创建响应式对象 function createReactiveObject( target: unknown, toProxy: WeakMap<any, any>, toRaw: WeakMap<any, any>, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) { // 情形1. target非对象直接返回 if (!isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) } return target } // target already has corresponding Proxy // 情形2. target已经有对应的Proxy代理(已经被代理过) let observed = toProxy.get(target) if (observed !== void 0) { return observed } // target is already a Proxy // 情形3. target本身是一个Proxy对象, 直接返回 if (toRaw.has(target)) { return target } // only a whitelist of value types can be observed. // 情形4. 不在被观察的白名单中 if (!canObserve(target)) { return target } // 根据target.cnnstructor区分不同target的handler, 关于两者此处不展开讲,可自行阅读源码 // baseHandlers: 普通对象的handler对象 // collectionHandlers: Set/WeakSet/Map/WeakMap的handler对象 const handlers = collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers observed = new Proxy(target, handlers) toProxy.set(target, observed) // 存储原生=>响应式数据映射表, 联系情形2, 可知目的: 防止reactive已经被reactive的值, 导致多次Proxy toRaw.set(observed, target) // 存储响应式=>原生数据映射表, 联系情形3, 可知目的: 防止reactive已经被reactive的值, 导致多次Proxy return observed } |
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | const origin = {count: 0, info: {name: 'xxl', age: 18}} const state = reactive(origin) const fn1 = () => {console.log(state.count)} const fn2 = () => {console.log(state.info.name)} const fn3 = () => {console.log(state.info.name, state.count)} // 依赖收集 const effect1 = effect(fn1) const effect2 = effect(fn2) const effect3 = effect(fn3) // 响应触发 state.count state.count++ state.info.name = 'ada' |
初始化阶段(代理):
1 2 3 4 | reactive(target:any):UnwrapNestedRef => 调用 CreateReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) => 调用 new Proxy(target, baseHandlers|collectionHandlers), 返回observed 若target是深层结构, 重复以上步骤 |
该模块流程图如下:
effect.ts
effect 类似于 Vue2.x中的 Watcher(观察者)
依赖收集器部分
首先先看下依赖收集器:
1 2 3 | type Dep = Set<ReactiveEffect> type KeyToDepMap = Map<any, Dep> const targetMap = new WeakMap<any, KeyToDepMap>() |
上面的代码可以用以下图来更清晰的表示:
整体可表示:
举个栗子:
1 2 3 4 5 | const origin = {count: 0, info: {name: 'xxl', age: 18}} const statte = reactive(origin) effect(() => {consoel.log(state.count)}) // effect1 effect(() => {consoel.log(state.info.name)}) // effect2 effect(() => {consoel.log(state.info.name +'is'+ state.count)}) // effect3 |
上面代码的依赖如下:
综合以上, 从代码设计来看, 个人感觉和Vue2.x的Dep依赖收集器思路基本相同
依赖收集部分
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | // 监听函数接口(混合类型接口) export interface ReactiveEffect<T = any> { (): T // 函数类型 _isEffect: true // 监听函数的标志 active: boolean // 监听函数是否在运行, stop后为false raw: () => T // raw === fn , 监听函数的原始函数 deps: Array<Dep> // 包含与此effect相关的所有Dep的数组 options: ReactiveEffectOptions // 配置项, 见下 } // 监听函数配置项 export interface ReactiveEffectOptions { lazy?: boolean // 是否延迟创建监听函数 computed?: boolean scheduler?: (run: Function) => void //调度器,可以看作是节点,当effect因为依赖改变而需要运行时,需要手动运行调度器运行 onTrack?: (event: DebuggerEvent) => void // 追踪事件,监听effect内的set操作 onTrigger?: (event: DebuggerEvent) => void // 触发事件,监听effect的依赖项set onStop?: () => void } // 根据监听函数的标志`_isEffect`来判断是否为监听函数 export function isEffect(fn: any): fn is ReactiveEffect { return fn != null && fn._isEffect === true } // EMPTY_OBJ = {} export function effect<T = any>( fn: () => T, options: ReactiveEffectOptions = EMPTY_OBJ ): ReactiveEffect<T> { //如果fn已经是effect,则将fn重置为它的原始函数 if (isEffect(fn)) { fn = fn.raw } // 创建监听函数 const effect = createReactiveEffect(fn, options) // function reactiveEffect(...args: unknown[]): unknown {return run(effect, fn, args)} if (!options.lazy) { effect() // options.lazy 默认为false, 所以立即执行一次监听函数 } return effect } // 创建 effect function createReactiveEffect<T = any>( fn: () => T, options: ReactiveEffectOptions ): ReactiveEffect<T> { const effect = function reactiveEffect(...args: unknown[]): unknown { return run(effect, fn, args) // 执行effect函数时, 调用下面的run() } as ReactiveEffect effect._isEffect = true effect.active = true effect.raw = fn // 把回调fn赋值给.raw属性 effect.deps = [] effect.options = options return effect } // effect堆栈 export const effectStack: ReactiveEffect[] = [] function run(effect: ReactiveEffect, fn: Function, args: unknown[]): unknown { if (!effect.active) { // 当调用stop()停止监听函数的响应式, 直接返回执行的原始函数, 不会被依赖收集 return fn(...args) } if (!effectStack.includes(effect)) { cleanup(effect) // 每次执行run(), 清除该effect下的deps, 是为了防止 fn 函数中访问的响应数据属性改动的情况,此时需要重新收集相关属性依赖 try { effectStack.push(effect) // 运行前先把effect压入栈 return fn(...args) // 执行原始函数并返回, 因为fn中引用了依赖数据, 执行fn触发track依赖收集 } finally { effectStack.pop() // 运行完再把effect推出栈 } } } // 依赖收集 export function track(target: object, type: TrackOpTypes, key: unknown) { if (!shouldTrack || effectStack.length === 0) { return } const effect = effectStack[effectStack.length - 1] // 取出栈顶的effect, 即是与当前key相关的effect, 因为执行effect()函数=>run()函数, push入该effect let depsMap = targetMap.get(target) if (depsMap === void 0) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (dep === void 0) { depsMap.set(key, (dep = new Set())) } if (!dep.has(effect)) { dep.add(effect) // 为dep添加相关effect effect.deps.push(dep) // 并把与此effect相关的dep全push到它的deps数组中 if (__DEV__ && effect.options.onTrack) { effect.options.onTrack({ effect, target, type, key }) } } } |
依赖收集阶段:
上图中
1 2 3 4 | effect(T=any) 1 --> createReactiveEffect(fn:()=>T, options: ReactiveEffectOptions):ReactiveEffect<T>, 返回effect 2 --> 先执行一次`effect()` 3 --> 调用 run(effect: ReactiveEffect, fn: Function, args: unknown[]): 1. cleanup(effect) => 2. effectStack.push(effect) => 3. fn(...args) => fn()中含有依赖数据, 触发getter `track()` 依赖收集开始({dep.add(effect); effect.deps.push(dep);}) => 4. effectStack.pop(effect) |
响应触发部分
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 74 75 76 77 78 79 | // 触发响应, 根据操作类型获取effect, 并区分computed添加到队列之后遍历执行effect export function trigger( target: object, type: OperationTypes, key?: unknown, extraInfo?: DebuggerEventExtraInfo ) { const depsMap = targetMap.get(target) if (depsMap === void 0) { // never been tracked return } const effects = new Set<ReactiveEffect>() const computedRunners = new Set<ReactiveEffect>() if (type === OperationTypes.CLEAR) { // collection being cleared, trigger all effects for target depsMap.forEach(dep => { addRunners(effects, computedRunners, dep) }) } else { // schedule runs for SET | ADD | DELETE if (key !== void 0) { addRunners(effects, computedRunners, depsMap.get(key)) } // also run for iteration key on ADD | DELETE 数组的push/pop if (type === OperationTypes.ADD || type === OperationTypes.DELETE) { const iterationKey = isArray(target) ? 'length' : ITERATE_KEY // 原始对象为数组 addRunners(effects, computedRunners, depsMap.get(iterationKey)) } } const run = (effect: ReactiveEffect) => { scheduleRun(effect, target, type, key, extraInfo) } // Important: computed effects must be run first so that computed getters // can be invalidated before any normal effects that depend on them are run. // 必须先运行computed effects,以便computed getter可能在运行任何依赖于它们的normal effects之前失效 computedRunners.forEach(run) effects.forEach(run) } // 添加effect function addRunners( effects: Set<ReactiveEffect>, computedRunners: Set<ReactiveEffect>, effectsToAdd: Set<ReactiveEffect> | undefined ) { if (effectsToAdd !== void 0) { effectsToAdd.forEach(effect => { if (effect.options.computed) { computedRunners.add(effect) } else { effects.add(effect) } }) } } function scheduleRun( effect: ReactiveEffect, target: object, type: OperationTypes, key: unknown, extraInfo?: DebuggerEventExtraInfo ) { if (__DEV__ && effect.options.onTrigger) { const event: DebuggerEvent = { effect, target, key, type } effect.options.onTrigger(extraInfo ? extend(event, extraInfo) : event) } if (effect.options.scheduler !== void 0) { effect.options.scheduler(effect) } else { effect() } } |
响应触发阶段:
整体流程图图下(
具体代码调用过程如下:
1 | trigger(target: object, type: TriggerOpTypes, key?: unknown) => 根据TriggerOpTypes和key获取effects, 调用 addRunner(effects, computedRunners, effectsToAdd)分类, 分为effects(normal effects)和computedRunners(computed effects) => 遍历effects,执行回调函数run() => 调用scheduleRun()执行effect, 即fn() |
computed.ts
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 74 75 76 | // 返回值是一个Ref类型数据 export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T> export function computed<T>( options: WritableComputedOptions<T> ): WritableComputedRef<T> export function computed<T>( getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T> ) { let getter: ComputedGetter<T> let setter: ComputedSetter<T> if (isFunction(getterOrOptions)) { getter = getterOrOptions setter = __DEV__ ? () => { console.warn('Write operation failed: computed value is readonly') } : NOOP } else { getter = getterOrOptions.get setter = getterOrOptions.set } let dirty = true let value: T // runner是effect函数, 返回effect const runner = effect(getter, { lazy: true, // mark effect as computed so that it gets priority during trigger // 将效果标记为计算,以便在触发期间获得优先级 computed: true, // 因为这里设置的调度器,依赖触发tirgger事件只是将dirty变为true scheduler: () => { dirty = true } }) return { _isRef: true, // expose effect so computed can be stopped effect: runner, get value() { if (dirty) { value = runner() dirty = false } // When computed effects are accessed in a parent effect, the parent // should track all the dependencies the computed property has tracked. // This should also apply for chained computed properties. trackChildRun(runner) // computed(fn)的返回值再次被监听 return value }, set value(newValue: T) { setter(newValue) } } as any } // 让依赖computed的effect实现监听逻辑 function trackChildRun(childRunner: ReactiveEffect) { if (effectStack.length === 0) { return } // 获取父级effect const parentRunner = effectStack[effectStack.length - 1] // 遍历子级,也即是本effect,的deps for (let i = 0; i < childRunner.deps.length; i++) { const dep = childRunner.deps[i] // 如果子级的某dep中没有父级effect,则将父级effect添加本dep中,然后更新父级effect的deps if (!dep.has(parentRunner)) { dep.add(parentRunner) // (1) parentRunner.deps.push(dep) // (2) } } } |
计算函数自身也是一个effect,之前我们说过,它的deps存着所有存着它的dep。而这个dep又指向targetMap中的相应数据。
由于都是引用数据,所以只要把父级effect补充到computed.deps(见上面的(1):
trackChildRun会将子Effect的依赖加入父Effect的依赖,这样在子Effect的依赖触发trigger事件时,子effect不会调用,但会把dirty变为true,父effect会调用,父effect内部对Ref值进行读操作,这时子effect调用将内部value改为新值。这样父effect就不会错过子effect的trigger事件了
例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 | const value = reactive({ foo: 1 }) const cValue = computed(() => value.foo) // effect 子 let dummy // 父 effect(() => { dummy = cValue.value }) console.log(dummy) // 1 value.foo = 4 //dummy === 4 |
而把computed.deps添加到父级effect的deps中(见上面的(2):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const value = reactive({ foo: 0 }) const getter1 = () => value.foo const getter2 = () => { return c1.value + 1 } const c1 = computed(getter1) const c2 = computed(getter2) let dummy effect(() => { dummy = c2.value }) // console.log(dummy) // 1 value.foo++ // dummy === 2 |
计算属性:
1 | computed(fn) => 生成computed 的 effect[即依赖收集阶段中第1步], 并返回 Ref => 使用返回的 Ref的value, 触发它的getter, 将运行runner() 函数 => 依赖收集阶段的第3步 |
Q:
ref VS reactive
对于基本数据类型,函数传递或者对象解构时,会丢失原始数据的引用,换言之,我们没法让基本数据类型,或者解构后的变量(如果它的值也是基本数据类型的话),成为响应式的数据。
- 通过创建一个对象(Ref), 将原始数据保存在Ref的属性value当中,再将它的引用返回给使用者
- 通过
toRefs(object) 函数, 解决对象的解构丢失原始数据引用的问题
通过遍历对象,将每个属性值都转成Ref数据,这样解构出来的还是Ref数据,自然就保持了响应式数据的引用
toRefs 解决的问题就是,开发者在函数中错误的解构 reactive,来返回基本类型。const { x, y } = = reactive({ x: 1, y: 2 }) ,这样会使 x, y 失去响应式,于是官方提出了toRefs 方案,在函数返回时,将 reactive 转为 refs,来避免这种情况。
总的来说, reactive 目前支持的类型为 Object|Array|Map|Set|WeakMap|WeakSet , refs支持的类型为基本数据类型, toRefs解决对象解构赋值后引用丢失问题
具体可参考: https://vue-composition-api-rfc.netlify.com/#ref-vs-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 | // test case: test('toRefs', () => { const a = reactive({ x: 1, y: 2 }) const { x, y } = toRefs(a) expect(isRef(x)).toBe(true) expect(isRef(y)).toBe(true) expect(x.value).toBe(1) expect(y.value).toBe(2) // source -> proxy a.x = 2 a.y = 3 expect(x.value).toBe(2) expect(y.value).toBe(3) // proxy -> source x.value = 3 y.value = 4 expect(a.x).toBe(3) expect(a.y).toBe(4) } |
深度侦测实现
1 2 3 4 5 6 | function createGetter() { return function get(target, key, receiver) { const res = Reflect.get(target, key, receiver) return isObject(res) ? reactive(res) : res // 通过对Reflect.get()的返回结果进行reactive递归调用, 达到深度侦测 } } |
参考: https://juejin.im/post/5d99be7c6fb9a04e1e7baa34
其他细节问题
Q: reactive.ts中createReactiveObject函数中已经有toProxy.set(target, observed), 为啥还需要 toRaw.set(observed, target), 它存在的意义?
A: 具体可看文中该部分注释, 联系上下文, 主要是为了优化包装后的对象再次被传入的情况,防止多次proxy, 其实两者都 起到了缓存的作用
Q: computed.ts 中 trackChildRun函数的作用? 或者可以说
A:
1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const value = reactive({ foo: 0 }) const getter1 = () => value.foo const getter2 = () => { return c1.value + 1 } const c1 = computed(getter1) const c2 = computed(getter2) let dummy effect(() => { dummy = c2.value }) // console.log(dummy) value.foo++ |
上面的代码的effect与dep的关系图如下:
若注释掉源码中的
其中
1 2 3 | effect1: 是指effect.raw = ()=>{value.foo}的effect effect2: 是指effect.raw = ()=>{return c1.value + 1}的effect effect3: 是指effect.raw = ()=>{dummy = c2.value}的effect |
关于这个问题, 可自行实验, [累死了]
2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 依赖于computed的effect 依赖追踪 const value = reactive({ foo: 1 }) const cValue = computed(() => value.foo) // effect 子, const runner = effecf, 返回Ref let dummy // 父 effect(() => { dummy = cValue.value }) value.foo = 4 console.log(dummy) // console.log(cValue.value) |
捋清了上面的问题,这个就很好解了,具体不详细描述了,理解不来,可在自己的草稿本画画~
最后
说下关于如何调试阅读源码, 我自己的方法是:
- 单元测试: 先到根目录安装下包
npm install , 再运行下 reactive模块下的测试用例
jest packages/reactivity/__tests__/xxxx.spec.ts - 打包编译成js源码, 再根据API, 写栗子测试, 这个在浏览器运行, 可以通过断点查看数据结构及流程, 关于如何打包可参考 https://juejin.im/post/5d9da45af265da5b8072de5d#heading-3
最后的最后, 说下自己的感觉,不喜勿喷,看源码应该有耐心,可参考他人优秀的分析文章,再认真理思绪,同时最重要的是思考这样设计的用意,是否可再优化. 哈哈,终于写完了~~~
参考
https://juejin.im/post/5d9da45af265da5b8072de5d#heading-3
https://juejin.im/post/5db837be51882564430c3f69
https://juejin.im/post/5d99be7c6fb9a04e1e7baa34
https://zhuanlan.zhihu.com/p/85978064
https://jooger.me/article/5da7deadfef8db6999d53701
https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md