
本篇文章将会围绕 Vue3 的另外一个主要的文件夹 reactivity 来进行讲解,也就是 Vue3 中对外暴露的 compositionApi 的部分,,越来越有 React Hooks 的味道了。reactivity 文件夹下面包含多个文件,主要功能在于 computed、effect、reactive、ref;其他的文件是为其进行服务的,另外还有一个主入口文件 index。reactivity 下面对外暴露的所有 api 可见下图,我们本篇文件会结合使用对这些功能进行源码分析。

# 正文
正文在这里,正式开始。
# computed
computed 的含义与 Vue2 中的含义是一样的,计算属性;使用方式也是和 Vue2 中差不多的,有两种使用方式:
# computed 使用
| const {reactive, readonly, computed, ref} = Vue; | |
| const app = Vue.createApp({}); | |
| app.component('TestComponent', { | |
| setup(props) { | |
|         // reactive | |
| const state = reactive({ | |
| count: 0, | |
| number: 10 | |
| }) | |
|         // computed getter | |
| const computedCount = computed(() => { | |
| return state.count + 10 | |
| }) | |
|         // computed set get | |
| const computedNumber = computed({ | |
| get: () => { | |
| return state.number + 100 | |
| }, | |
| set: (value) => { | |
| state.number = value - 50 | |
|             } | |
| }) | |
| const changeCount = function(){ | |
| state.count++; | |
| computedNumber.value = 200 | |
|         } | |
| return { | |
|             state, | |
|             changeCount, | |
|             computedCount, | |
| computedNumber | |
|         } | |
| }, | |
| template: ` | |
| <div> | |
| <h2>init count:<i></i></h2> | |
| <h2>computedCount:<i></i></h2> | |
| <h2>computedNumber:<i></i></h2> | |
| <button @click="changeCount">changeCount</button> | |
| </div> | |
|     ` | |
| }) | |
| app.mount('#demo') | 
上面代码可以看到两次对 computed 的使用,第一次传递的是一个函数,第二次传递的是一个包含 get 和 set 的对象。
# computed 源码分析
接下来,咱们来看下 computed 的源码:
| // @file packages/reactivity/src/computed.ts | |
| 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 | |
|   } | |
| return new ComputedRefImpl( | |
|     getter, | |
|     setter, | |
| isFunction(getterOrOptions) || !getterOrOptions.set | |
| ) as any | |
| } | 
上面是 computed 的入口的源码,此处和 Vue2 中的写法是一样的,都是对参数进行判断,生成 getter 和 setter,这里最后调用的是 ComputedRefImpl;
| // packages/reactivity/src/computed.ts | |
| class ComputedRefImpl<T> { | |
| private _value!: T | |
| private _dirty = true | |
| public readonly effect: ReactiveEffect<T> | |
| public readonly __v_isRef = true; | |
| public readonly [ReactiveFlags.IS_READONLY]: boolean | |
| constructor( | |
| getter: ComputedGetter<T>, | |
| private readonly _setter: ComputedSetter<T>, | |
| isReadonly: boolean | |
| ) { | |
| this.effect = effect(getter, { | |
| lazy: true, | |
| scheduler: () => { | |
| if (!this._dirty) { | |
| this._dirty = true | |
| trigger(toRaw(this), TriggerOpTypes.SET, 'value') | |
|         } | |
|       } | |
| }) | |
| this[ReactiveFlags.IS_READONLY] = isReadonly | |
|   } | |
| get value() { | |
| if (this._dirty) { | |
| this._value = this.effect() | |
| this._dirty = false | |
|     } | |
| track(toRaw(this), TrackOpTypes.GET, 'value') | |
| return this._value | |
|   } | |
| set value(newValue: T) { | |
| this._setter(newValue) | |
|   } | |
| } | 
如上,是 ComputedRefImpl 的源码。ComputedRefImpl 是一个 class,内部包含_value、_dirty、effect、__v_isRef、ReactiveFlags.IS_READONLY 等属性,还包括 constructor 和 get、set 等函数。了解的同学都知道,会首先调用构造函数也就是 constructor;调用 effect 为 effect 属性赋值,把 isReadonly 赋值给 ReactiveFlags.IS_READONLY 属性,关于 effect,咱们后面讲这块。此时 ComputedRefImpl 执行完成。
当获取当前 computed 的值的时候,如上面使用中 computedCount 在 template 中进行获取值的时候,会调用上面 class 内的 get 方法,get 方法内部调用的是 this.effect 进行数据的获取,_dirty 属性是为了数据的缓存,依赖未发生变化,则不会调用 effect,使用之前的 value 进行返回。track 是跟踪当前 get 调用的轨迹。
当为 computed 赋值的时候,如上面使用中 computedNumber.value = 200 的时候,,会调用上面 class 内的 set 方法,set 内部还是调用了之前传递进来的函数。
# reactive
接下来对 reactive 的讲解
# reactive 使用
reactive 官网给的解释是: 返回对象的响应式副本 。先来看下 reactive 的使用
| const {reactive} = Vue; | |
| const app = Vue.createApp({}); | |
| app.component('TestComponent', { | |
| setup(props) { | |
|         // reactive | |
| const state = reactive({ | |
| count: 0 | |
| }) | |
| const changeCount = function(){ | |
| state.count++; | |
|         } | |
| return { | |
|             state, | |
| changeCount | |
|         } | |
| }, | |
| template: ` | |
| <div> | |
| <h2>reactive count:<i></i></h2> | |
| <button @click="changeCount">changeCount</button> | |
| </div> | |
|     ` | |
| }) | |
| app.mount('#demo') | 
当点击 changeCount 的时候,state.count 会 ++,同时映射到 h2-dom。
# reactive 源码解读
| // @file packages/reactivity/src/reactive.ts | |
| 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, | |
| mutableCollectionHandlers | |
|   ) | |
| } | 
如上源码可以看到,如果 target 有值并且 target 的 [ReactiveFlags.IS_READONLY] 属性,也就是__v_isReadonly 为 true 的话,会直接返回当前对象,不做任何处理,后面对 state.count 的改变也不会映射到 dom 当中。如果不满足上面条件,则会调用 createReactiveObject 函数,传递 4 个参数:
- target 为原始对象;
- 第二个是 isReadonly,为 false;
- 第三个参数 mutableHandlers 是 reactive 对应的处理函数;
- 第四个参数是对于集合类型的对象进行处理的函数。
关于这个核心的函数,我们待会来进行讲解。
# readonly 使用
现在我们来看下 Vue3 提供给我们的 reactivity 下面的第二个 api:readonly。
官网给出的定义是: 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。只读代理是深层的:访问的任何嵌套 property 也是只读的
| const {readonly} = Vue; | |
| const app = Vue.createApp({}); | |
| app.component('TestComponent', { | |
| setup(props) { | |
| const read = readonly({count: 1}) | |
| const changeRead = function(){ | |
| read.count++; | |
|         } | |
| return { | |
|             read, | |
| changeRead | |
|         } | |
| }, | |
| template: ` | |
| <div> | |
| <h2>readonly count:<i></i></h2> | |
| <button @click="changeRead">changeRead</button> | |
| </div> | |
|     ` | |
| }) | |
| app.mount('#demo') | 
上面代码,是 readonly 的使用,在此试验了一下对 readonly 返回后的结果 read,进行了改变的尝试,发现是改变不了的,属于只读,同时还会打印警告 Set operation on key "count" failed: target is readonly.
# readonly 源码解读
| // @file packages/reactivity/src/reactive.ts | |
| export function readonly<T extends object>( | |
| target: T | |
| ): DeepReadonly<UnwrapNestedRefs<T>> { | |
| return createReactiveObject( | |
|     target, | |
| true, | |
|     readonlyHandlers, | |
| readonlyCollectionHandlers | |
|   ) | |
| } | 
上面就是 readonly 的源码入口,与 reactive 一样,都是调用的 createReactiveObject 函数:
- 第一个参数还是 target;
- 第二个是 isReadonly,为 true;
- 第三个参数 readonlyHandlers 是 readonly 对应的处理函数;
- 第四个参数是对于集合类型的对象进行处理的 readonly 所对应的函数。
# shallowReactive 使用
官网文档给的解释: 创建一个响应式代理,该代理跟踪其自身 property 的响应性,但不执行嵌套对象的深度响应式转换 (暴露原始值)。 来看下 shallowReactive 的使用
| const {shallowReactive} = Vue; | |
| const app = Vue.createApp({}); | |
| app.component('TestComponent', { | |
| setup(props) { | |
| const state = shallowReactive({ | |
| foo: 1, | |
| nested: { | |
| bar: 2 | |
|             } | |
| }) | |
| const change = function(){ | |
| state.foo++ | |
| state.nested.bar++ | |
|         } | |
| return { | |
|             state, | |
| change | |
|         } | |
| }, | |
| template: ` | |
| <div> | |
| <h2>foo:<i></i></h2> | |
| <h2>bar:<i></i></h2> | |
| <button @click="change">change</button> | |
| </div> | |
|     ` | |
| }) | |
| app.mount('#demo') | 
上面代码基本是完全按照官网来写的,不过,试了下效果和官网上的效果不一样,并不是 shallow 类型,而是对内部的属性也进行了监听,bar 的改变也会响应式的反映到 dom 当中去。也不知道是我姿势不对,还是 Vue3 的 bug。
# shallowReactive 源码解读
| // @file packages/reactivity/src/reactive.ts | |
| export function shallowReactive<T extends object>(target: T): T { | |
| return createReactiveObject( | |
|     target, | |
| false, | |
|     shallowReactiveHandlers, | |
| shallowCollectionHandlers | |
|   ) | |
| } | 
上面就是 shallowReactive 的源码入口,与 reactive 和 readonly 一样,都是调用的 createReactiveObject 函数:
- 第一个参数还是 target; 
- 第二个是 isReadonly,为 false; 
- 第三个参数 shallowReactiveHandlers 是 shallowReactive 对应的处理函数; 
- 第四个参数是对于集合类型的对象进行处理的 shallowReactive 所对应的函数。 
# shallowReadonly 使用
官网给出的解释: 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。 来看下使用:
| const {shallowReadonly} = Vue; | |
| const app = Vue.createApp({}); | |
| app.component('TestComponent', { | |
| setup(props) { | |
| const state = shallowReadonly({ | |
| foo: 1, | |
| nested: { | |
| bar: 2 | |
|         } | |
| }) | |
| const change = function(){ | |
| state.foo++ | |
| state.nested.bar++ | |
|     } | |
| return { | |
|         state, | |
| change | |
|     } | |
| }, | |
| template: ` | |
| <div> | |
| <h2>foo:<i></i></h2> | |
| <h2>bar:<i></i></h2> | |
| <button @click="change">change</button> | |
| </div> | |
| ` | |
| }) | |
| app.mount('#demo') | 
上面代码基本是完全按照官网来写的,foo 的改变不被允许,按照官网说明 state.nested.bar 是允许被改变的,在上面例子中,发现 state.nested.bar 的值是会改变的,但是不会响应到 dom 上。
# shallowReadonly 源码解读
| // @file packages/reactivity/src/reactive.ts | |
| export function shallowReadonly<T extends object>( | |
| target: T | |
| ): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> { | |
| return createReactiveObject( | |
| target, | |
| true, | |
| shallowReadonlyHandlers, | |
| readonlyCollectionHandlers | |
| ) | |
| } | 
上面就是 shallowReadonly 的源码入口,与 reactive 和 readonly 一样,都是调用的 createReactiveObject 函数:
- 第一个参数还是 target; 
- 第二个是 isReadonly,为 true; 
- 第三个参数 shallowReadonlyHandlers 是 shallowReadonly 对应的处理函数; 
- 第四个参数是对于集合类型的对象进行处理的 shallowReadonly 所对应的函数。 
# isReadonly
isReadonly: 检查对象是否是由 readonly 创建的只读代理。
使用如下:
| const only = readonly({ | |
| count: 1 | |
| }) | |
| isOnly = isReadonly(only) // true | 
源码如下:
| export function isReadonly(value: unknown): boolean { | |
| return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]) | |
| } | 
ReactiveFlags.IS_READONLY 是一个字符串,值为:__v_isReadonly,挂到对象上面就是属性,判断当前对象的__v_isReadonly 属性是否是 true,并返回。
# isReactive
isReadonly: 检查对象是否是 reactive 创建的响应式 proxy。
使用如下:
| const tive = reactive({ | |
| count: 1 | |
| }) | |
| isOnly = isReactive(tive) // true | 
源码如下:
| export function isReactive(value: unknown): boolean { | |
| if (isReadonly(value)) { | |
| return isReactive((value as Target)[ReactiveFlags.RAW]) | |
|   } | |
| return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]) | |
| } | 
首先调用了上面提到的 isReadonly 方法判断是否是 readonly 创建的对象;如果是的话,则进一步使用当前对象的 RAW 属性调用 isReactive 来判断;如果不是则判断__v_isReactive 是否为 true;返回判断的结果。
ReactiveFlags.RAW 是一个字符串,值为:__v_raw,挂到对象上面就是属性,也就是原始对象,判断是否是 reactive 代理的原始对象;
ReactiveFlags.IS_READONLY 也是一个字符串,值为:__v_isReactive,挂到对象上面就是属性
# isProxy
isProxy: 检查对象是否是 reactive 或 readonly 创建的代理。
使用如下:
| const tive = reactive({ | |
| count: 1 | |
| }) | |
| const only = readonly({ | |
| count: 1 | |
| }) | |
| is1 = isProxy(tive) // true | |
| is2 = isProxy(only) // true | 
源码如下:
| export function isProxy(value: unknown): boolean { | |
| return isReactive(value) || isReadonly(value) | |
| } | 
调用上面提到的 isReadonly 方法和 isReactive 判断是否是 proxy 的对象。
# markRaw
markRaw:标记一个对象,使其永远不会转换为代理。返回对象本身。
使用如下:
| const foo = markRaw({}) | |
| console.log(isReactive(reactive(foo))) // false | |
| const bar = reactive({ foo }) | |
| console.log(isReactive(bar)) // true | |
| console.log(isReactive(bar.foo)) // false | 
从上面使用中可以看到,markRaw 只对当前对象本身有效,被标记的对象作为属性的时候,大对象 bar 还是可以进行响应式处理的,但是 bar 里面的当前被标记的对象 foo,还是一个非响应式对象,永远是 foo 对象本身。
| export function markRaw<T extends object>(value: T): 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 | |
| }) | |
| } | 
上面可以看到 markRaw 的源码,就是给要标记的对象增加了一个属性 (ReactiveFlags.SKIP, 也就是__v_skip),并赋值 true,所有要给当前对象进行响应式处理的时候,都会被忽略。
# toRaw
toRaw:返回 reactive 或 readonly 代理的原始对象。这是一个转义口,可用于临时读取而不会引起代理访问 / 跟踪开销,也可用于写入而不会触发更改。不建议保留对原始对象的持久引用。请谨慎使用。
既然 Vue 让咱们谨慎使用,咱们还是在可以不使用的的时候不使用的好,这个就是把代理的原始对象进行返回。
| const obj = { | |
| project: 'reactive' | |
| } | |
| const reactiveObj = reactive(obj) | |
| console.log(toRaw(reactiveObj) === obj) // true | 
源码:
| export function toRaw<T>(observed: T): T { | |
| return ( | |
| (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed | |
|     ) | |
| } | 
返回当前对象的 ReactiveFlags.RAW (也就是__v_raw) 属性指向的对象,也就是对象本身,关于在什么地方给 ReactiveFlags.RAW 赋值的,后面会看到这部分。
# createReactiveObject
上面的 reactive、readonly、shallowReactive、shallowReadonly,都用到了 createReactiveObject 函数,现在咱们来看看这个函数的源码。
| function createReactiveObject(target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<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 proxyMap = isReadonly ? readonlyMap : reactiveMap | |
| 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 | |
| } | 
源码解读:
- 首先,target 得是一个对象,不是对象的话,直接返回当前值;当然 Vue3 也提供了对值的响应式的方法:ref,后面讲。
- 判断有原始对象且,不是只读或者不是响应式的对象,则返回当前对象,这个地方真 TM 绕。
- 根据是否是 isReadonly,获取到代理存储的 map,,如果之前代理过,已经存在,则把之前代理过的 proxy 返回。
- 判断 target 的类型,getTargetType 内部会对 target 对象进行判断,返回是 common、collection 或者 invalid;如果不可用类型 (invalid),则直接返回当前对象。此处会用到上面讲到的__v_skip。可用的类型就两个,一个是 common,一个是 collection;
- 接下来就是没有代理过,获取代理的过程。 new Proxy,如果是 collection 则使用传递进来的 collectionHandlers,否则 (也就是 common) 则使用 baseHandlers;
- 代理存储所使用的 map,存储当前 proxy;
- 返回当前 proxy。
通过上面 reactive、readonly、shallowReactive、shallowReadonly 的讲解,可以看到对于集合和 common 类型,提供了几种不同的处理对象,对象中所包含的内容也是不一样的,咱们在这里来对比着看下:
# basehandler:

如上图,可以看到,basehandler 里面所提供的函数,我们一一来看下。
# deleteProperty
| // @file packages/reactivity/src/baseHandlers.ts | |
| 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 | |
| } | 
- 获取当前对象是否有当前 key => hadKey; 
- 获取到当前的 value 存储为 oldValue; 
- 调用 Reflect.deleteProperty 进行对当前对象 target 删除当前 key 的操作,返回结果为是否删除成功 ->result; 
- 删除成功,并且有当前 key,则调用 trigger,触发 effect。 
- 返回删除是否成功的结果。 
# ownKeys
| // @file packages/reactivity/src/baseHandlers.ts | |
| function ownKeys(target: object): (string | number | symbol)[] { | |
| track(target, TrackOpTypes.ITERATE, ITERATE_KEY) | |
| return Reflect.ownKeys(target) | |
| } | 
这个函数很简单了就,获取 target 对象自己的属性 key;跟踪获取的轨迹,然后调用 Reflect.ownKeys 获取结果。
# has
| // @file packages/reactivity/src/baseHandlers.ts | |
| 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 | |
| } | 
- 调用 Reflect.has 获取当前对象是否有当前 key; 
- 不是 Symbol 类型的 key,或者不是 Symbol 本身的属性,调用 track 跟踪 has 调用的轨迹。 
- 返回结果,result。 
# createSetter
| function createSetter(shallow = false) { | |
| return function set( | |
| target: object, | |
| key: string | symbol, | |
| value: unknown, | |
| receiver: object | |
| ): boolean { | |
| const oldValue = (target as any)[key] | |
| if (!shallow) { | |
| value = toRaw(value) | |
| if (!isArray(target) && isRef(oldValue) && !isRef(value)) { | |
| oldValue.value = value | |
| return true | |
|   } | |
| } else {} | |
| const hadKey = isArray(target) && isIntegerKey(key) | |
| ? Number(key) < target.length | |
| : hasOwn(target, key) | |
| 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) | |
|   } | |
| } | |
| return result | |
| } | |
| } | 
函数工厂,根据 shallow 生成 set 函数。set 函数接受 4 个参数:target 为目标对象;key 为设置的属性;value 为设置的值;receiver 为 Reflect 的额外参数 (如果遇到 setter,receiver 则为 setter 调用时的 this 值)。
- 首先获取到 oldValue; 
- 如果非浅响应式,也就是正式情况的时候,获取到 value 的原始对象并赋值给 value,如果 target 对象不是数组且 oldValue 是 ref 类型的响应式类型,并且新 value 不是 ref 类型的响应式,为 oldValue 赋值 (ref 类型的响应式对象,需要为对象的 value 赋值)。 
- 下面也就是深度响应式的代码逻辑了。 
- 如果是数组并且 key 是数字类型的,则直接判断下标,否则调用 hasOwn 获取,是否包含当前 key => hadKey; 
- 调用 Reflect.set 进行设置值; 
- 如果目标对象和 receiver 的原始对象相等,则 hadKey,调用 trigger 触发 add 操作;否则,调用 trigger 触发 set 操作。 
- 把 set 处理的结果返回,result。 
# createGetter
| 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.RAW && | |
| receiver === (isReadonly ? readonlyMap : reactiveMap).get(target) | |
| ) { | |
|   return target | |
| } | |
| const targetIsArray = isArray(target) | |
| if (targetIsArray && hasOwn(arrayInstrumentations, key)) { | |
| return Reflect.get(arrayInstrumentations, key, receiver) | |
| } | |
| const res = Reflect.get(target, key, receiver) | |
| const keyIsSymbol = isSymbol(key) | |
| if ( | |
| keyIsSymbol | |
| ? builtInSymbols.has(key as symbol) | |
| : key === `__proto__` || key === `__v_isRef` | |
| ) { | |
|   return res | |
| } | |
| if (!isReadonly) { | |
| track(target, TrackOpTypes.GET, key) | |
| } | |
| if (shallow) { | |
|   return res | |
| } | |
| if (isRef(res)) { | |
| const shouldUnwrap = !targetIsArray || !isIntegerKey(key) | |
| return shouldUnwrap ? res.value : res | |
| } | |
| if (isObject(res)) { | |
| return isReadonly ? readonly(res) : reactive(res) | |
| } | |
| return res | |
| } | |
| } | 
函数工厂,根据 shallow 生成 get 函数。get 函数接受 3 个参数:target 为目标对象;key 为设置的属性;receiver 为 Reflect 的额外参数 (如果遇到 setter,receiver 则为 setter 调用时的 this 值)。
- 如果 key 是__v_isReactive,则直接返回!isReadonly,通过上面的图可得知,reactive 相关的调用 createGetter,传递的是 false,也就是会直接返回 true; 
- 如果 key 是__v_isReadonly,则直接返回 isReadonly,同样的通过上面的图可以得知,readonly 相关的调用 createGetter,传递的是 true,也就是会直接返回 true; 
- 如果 key 是__v_raw 并且 receiver 等于 proxyMap 存储的 target 对象的 proxy,也就是获取原始对象,则直接返回 target; 
- 如果是数组的话,则会走自定义的方法,arrayInstrumentations;arrayInstrumentations 是和 Vue2 中对数组的改写是一样的逻辑; 
- 下面会对 key 进行判断,如果 Symbol 对象并且是 Set 里面自定义的方法;或者 key 为__proto__或__v_isRef,则直接把 - Reflect.get(target, key, receiver)获取到的值直接返回;
- 如果非只读情况下,调用 track 跟踪 get 轨迹; 
- 如果是 shallow,非深度响应式,也是直接把上面获取到的 res 直接返回; 
- 如果是 ref 对象,则会调用.value 获取值进行返回; 
- 剩下的情况下,如果得到的 res 是个对象,则根据 isReadonly 调用 readonly 或 reactive 获取值,进行返回; 
- 最后有一个 res 保底返回; 
# collectionHandler:

来看下 createInstrumentationGetter 的源码,上面图中三个都是调用此方法生成对应的处理对象。
| function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) { | |
| const instrumentations = shallow | |
| ? 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 | |
| } | |
| return Reflect.get( | |
| hasOwn(instrumentations, key) && key in target | |
|     ? instrumentations | |
| : target, | |
|   key, | |
| receiver | |
| ) | |
| } | |
| } | 
上面 createInstrumentationGetter 函数根据 isReadonly 和 shallow 返回一个函数;
- 根据 isReadonly 和 shallow,获取到对应的 instrumentations;此对象包含了对集合操作的所有方法; 
- 然后就把下面的函数进行了返回,createInstrumentationGetter 相当于是一个闭包; 
- 返回的函数里面在执行调用的时候,会先对 key 进行判断,如果访问的是 Vue 的私有变量,也就是上面的__v_isReactive、__v_isReadonly、__v_raw 等,会直接给出不同的返回; 
- 如果不是 Vue 的上面的三个私有变量,则会调用 Reflect.get 来获取对象的值;instrumentations,也就是重写的方法集合,不在此集合里面的,则会直接调用 target 自己的方法。 
# reactive 完结
至此,reactive 文件里面的这些方法咱们都梳理了一遍,简单的做了源码的分析和解读,感兴趣的读者可以深入源码研究下 Vue 中为何这样实现。
# Refs
接下来我们将开始对 ref 及其附属方法的使用和讲解。
# ref
首先,咱们对 ref 进行讲解,官网给出的解释是:接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value。
先来看下 ref 的使用。
| const {ref} = Vue; | |
| const app = Vue.createApp({}); | |
| app.component('TestComponent', { | |
| setup(props) { | |
| const count = ref(0) | |
| const obj = ref({number: 10}) | |
| const change = function(){ | |
| count.value++; | |
| obj.value.number++ | |
|         } | |
| return { | |
|             count, | |
|             obj, | |
| change | |
|         } | |
| }, | |
|     template: ` | |
|         <div> | |
| <h2>count:<i></i></h2> | |
| <h2>number:<i></i></h2> | |
| <button @click="change">change</button> | |
|         </div> | |
| ` | |
| }) | |
| app.mount('#demo') | 
上面是 ref 的使用,可以看到 ref 接受的是一个普通类型的值或者是一个对象,Vue 官网给出的例子是不包含传递对象的,其实这也就是 Vue 不提倡使用 ref 来响应式一个对象,如果是对对象的响应式,Vue 还是提倡使用上面 reactive 来实现;第二个需要注意点在于 template 中对 ref 对象的引用是不需要加上 value 属性来获取值,如上 ref 对象 count 在 js 中需要 count.value, 但是在 template 种只需 count 即可;
来看下 ref 的源码实现
| // @file packages/reactivity/src/ref.ts | |
| export function ref<T extends object>( | |
| value: T | |
| ): T extends Ref ? T : Ref<UnwrapRef<T>> | |
| export function ref<T>(value: T): Ref<UnwrapRef<T>> | |
| export function ref<T = any>(): Ref<T | undefined> | |
| export function ref(value?: unknown) { | |
| return createRef(value) | |
| } | |
| function createRef(rawValue: unknown, shallow = false) { | |
| if (isRef(rawValue)) { | |
|     return rawValue | |
|   } | |
| return new RefImpl(rawValue, shallow) | |
| } | |
| class RefImpl<T> { | |
| private _value: T | |
| public readonly __v_isRef = true | |
| constructor(private _rawValue: T, private readonly _shallow = false) { | |
| this._value = _shallow ? _rawValue : convert(_rawValue) | |
|   } | |
| get value() { | |
| track(toRaw(this), TrackOpTypes.GET, 'value') | |
| return this._value | |
|   } | |
| set value(newVal) { | |
| if (hasChanged(toRaw(newVal), this._rawValue)) { | |
| this._rawValue = newVal | |
| this._value = this._shallow ? newVal : convert(newVal) | |
| trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal) | |
|     } | |
|   } | |
| } | |
| const convert = <T extends unknown>(val: T): T => | |
| isObject(val) ? reactive(val) : val | |
| export const hasChanged = (value: any, oldValue: any): boolean => | |
| value !== oldValue && (value === value || oldValue === oldValue) | 
上面是按照运行轨迹来看的 Vue3 中 ref 的源码部分;根据 ref 的声明可以看到 ref 接受任何参数,返回类型为 Ref 对象,内部调用的是 createRef;
- createRef 函数内部会先对 value 进行判断,如果已经是 ref 对象的话,直接返回当前 value,否则就调用 new RefImpl 来生成 ref 对象进行返回。 
- constructor 里面会判断是否是浅响应_shallow,浅的话,直接返回_rawValue,否则调用 convert 来返回;可以看到除了私有属性_value 外,还有一个__v_isRef 的只读属性为 true; 
- convert 里面则会对 val 进行判断了,对象则调用 reactive,否则直接返回 val,此处也就可以看到上面 ref 也可以接受对象作为参数的缘由了。 
- get 里面会跟踪调用轨迹,track;返回当前 value; 
- set 里面会调用 hasChanged 判断是否发生了改变,此处会对 NaN 进行 check,因为 NaN 与啥都不相等;设置新的值,同时调用 trigger 触发 set 调用。 
# isRef
isRef 很明显就是判断是否是 ref 对象的方法。使用如下:
| const count = ref(0) | |
| const is = isRef(count) | |
| const is2 = isRef(10) | 
来看下源码,源码也很简单:
| export function isRef<T>(r: Ref<T> | unknown): r is Ref<T> | |
| export function isRef(r: any): r is Ref { | |
| return Boolean(r && r.__v_isRef === true) | |
| } | 
此处就使用到了 RefImpl 里面那个只读属性了,判断__v_isRef 是否为 true 就可以了。
# shallowRef
官网给出的解释:创建一个 ref,它跟踪自己的 .value 更改,但不会使其值成为响应式的。
shallowRef 的源码如下:
| export function shallowRef<T extends object>( | |
| value: T | |
| ): T extends Ref ? T : Ref<T> | |
| export function shallowRef<T>(value: T): Ref<T> | |
| export function shallowRef<T = any>(): Ref<T | undefined> | |
| export function shallowRef(value?: unknown) { | |
| return createRef(value, true) | |
| } | 
shallowRef 与 ref 的调用流程是一样的,不过是多了个参数,导致_shallow 为 true,就在 RefImpl 里面调用时,直接返回了当前 value,而不会进行到 convert 函数。
# unRef
官网解释:如果参数为 ref,则返回内部值,否则返回参数本身。 源码如下:
| export function unref<T>(ref: T): T extends Ref<infer V> ? V : T { | |
| return isRef(ref) ? (ref.value as any) : ref | |
| } | 
确实如官网所说,就一行代码,ref 对象则返回其 value,否则直接返回 ref。
# triggerRef
官网给出的解释:手动执行与 shallowRef 关联的任何效果。 ,比较模糊,通俗点就是手动触发一次 effect 的调用;
看下使用:
| const count = ref(0) | |
| const change = function(){ | |
| count.value++; | |
| triggerRef(count) | |
| } | |
| const shallow = shallowRef({ | |
| greet: 'Hello, world' | |
| }) | |
| watchEffect(() => { | |
| console.log(count.value) | |
| console.log(shallow.value.greet) | |
| }) | |
| shallow.value.greet = 'Hello, universe' | 
源码如下:
| export function triggerRef(ref: Ref) { | |
| trigger(ref, TriggerOpTypes.SET, 'value', __DEV__ ? ref.value : void 0) | |
| } | 
# toRef
官网给出的解释是:可以用来为源响应式对象上的 property 属性创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接。 简单描述就是为对象的一个属性增加一个引用,这个引用可以随意使用,响应式不变。来看下源码:
| export function toRef<T extends object, K extends keyof T>( | |
| object: T, | |
| key: K | |
| ): Ref<T[K]> { | |
| return isRef(object[key]) | |
| ? object[key] | |
| : (new ObjectRefImpl(object, key) as any) | |
| } | |
| class ObjectRefImpl<T extends object, K extends keyof T> { | |
| public readonly __v_isRef = true | |
| constructor(private readonly _object: T, private readonly _key: K) {} | |
| get value() { | |
| return this._object[this._key] | |
|   } | |
| set value(newVal) { | |
| this._object[this._key] = newVal | |
|   } | |
| } | 
这部分的代码比较简单,也比较容易读懂,和上面 RefImpl 一样的是都增加了一个只读的__v_isRef 属性。
# toRefs
官网对 toRefs 给出的解释是:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。 通俗点描述就是把响应式对象的每个属性,都变成 ref 对象。来看下源码:
| export function toRefs<T extends object>(object: T): ToRefs<T> { | |
| if (__DEV__ && !isProxy(object)) { | |
| console.warn(`toRefs() expects a reactive object but received a plain one.`) | |
|   } | |
| const ret: any = isArray(object) ? new Array(object.length) : {} | |
| for (const key in object) { | |
| ret[key] = toRef(object, key) | |
|   } | |
|   return ret | |
| } | 
这里尤为要求是一个响应式的对象,非响应式对象还会打印警告。for 循环调用上面讲到的 toRef 函数,把对象里面的每个属性都变为 ref 对象。
# customRef
官网给出的解释是:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数 来看下 customRef 的源码:
| class CustomRefImpl<T> { | |
| private readonly _get: ReturnType<CustomRefFactory<T>>['get'] | |
| private readonly _set: ReturnType<CustomRefFactory<T>>['set'] | |
| public readonly __v_isRef = true | |
| constructor(factory: CustomRefFactory<T>) { | |
| const { get, set } = factory( | |
| () => track(this, TrackOpTypes.GET, 'value'), | |
| () => trigger(this, TriggerOpTypes.SET, 'value') | |
|     ) | |
| this._get = get | |
| this._set = set | |
|   } | |
| get value() { | |
| return this._get() | |
|   } | |
| set value(newVal) { | |
| this._set(newVal) | |
|   } | |
| } | |
| export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> { | |
| return new CustomRefImpl(factory) as any | |
| } | 
相对应的,使用的时候,接受的是一个 factory,factory 是一个函数,参数为 track 和 trigger,同时 factory 的返回须包含两个函数,一个为 get,一个为 set。track 就是 effect 的 track,trigger 也是 effect 的 trigger;来看下使用:
| const {customRef} = Vue; | |
| const app = Vue.createApp({}); | |
| function useDebouncedRef(value, delay = 200) { | |
|     let timeout | |
| return customRef((track, trigger) => { | |
| return { | |
| get() { | |
| track() | |
|                 return value | |
| }, | |
| set(newValue) { | |
| clearTimeout(timeout) | |
| timeout = setTimeout(() => { | |
|                     value = newValue | |
| trigger() | |
| }, delay) | |
|             } | |
|         } | |
| }) | |
| } | |
| app.component('TestComponent', { | |
| setup(props) { | |
| return { | |
| text: useDebouncedRef('hello') | |
|         } | |
| }, | |
| template: ` | |
| <div> | |
| <input v-model="text" /> | |
| </div> | |
|     ` | |
| }) | |
| app.mount('#demo') | 
上面是 customRef 的使用的例子,和官网的例子是一样的,能够实现防抖,同时也能够显式的控制什么时候调用 track 来跟踪和什么时候来调用 trigger 来触发改变。
# Refs 完结
上面我们对 refs 里面的几种方法做了源码的解读和部分的 api 是如何使用的,关于 Vue3 为何提供了两种响应式的方案:reactive 和 Refs,这其实就和代码风格有关系了,有的同学习惯使用对象,而有的同学习惯使用变量,Vue3 为这两种方案都提供了,想用哪个用哪个。
# effect
其实可以看到上面好多地方都用到了这个方法,包括 effect、track、trigger 等都是 effect 里面提供的方法,effect 里面提供的方法属于 Vue 的内部方法,不对外暴露。下面我们挨个来看看这部分的源码,
# isEffect
isEffect 是为判断是否是有副作用的函数。来看下源码:
| export function isEffect(fn: any): fn is ReactiveEffect { | |
| return fn && fn._isEffect === true | |
| } | 
可以看到上面的判断,就是对函数的_isEffect 进行判断,非常简单。
# effect
effect 作为 Vue2 和 Vue3 中核心的部分,都有这个的概念,重中之重,来看下这部分的源码:
| export function effect<T = any>( | |
| fn: () => T, | |
| options: ReactiveEffectOptions = EMPTY_OBJ | |
| ): ReactiveEffect<T> { | |
| if (isEffect(fn)) { | |
| fn = fn.raw | |
|   } | |
| const effect = createReactiveEffect(fn, options) | |
| if (!options.lazy) { | |
| effect() | |
|   } | |
|   return effect | |
| } | |
| function createReactiveEffect<T = any>( | |
| fn: () => T, | |
|   options: ReactiveEffectOptions | |
| ): ReactiveEffect<T> { | |
| const effect = function reactiveEffect(): unknown { | |
| if (!effect.active) { | |
| return options.scheduler ? undefined : fn() | |
|     } | |
| if (!effectStack.includes(effect)) { | |
| cleanup(effect) | |
| try { | |
| enableTracking() | |
| effectStack.push(effect) | |
|         activeEffect = effect | |
| return fn() | |
| } finally { | |
| effectStack.pop() | |
| resetTracking() | |
| activeEffect = effectStack[effectStack.length - 1] | |
|       } | |
|     } | |
| } as ReactiveEffect | |
| effect.id = uid++ | |
| effect._isEffect = true | |
| effect.active = true | |
| effect.raw = fn | |
| effect.deps = [] | |
| effect.options = options | |
|   return effect | |
| } | |
| let shouldTrack = true | |
| const trackStack: boolean[] = [] | |
| export function pauseTracking() { | |
| trackStack.push(shouldTrack) | |
| shouldTrack = false | |
| } | |
| export function enableTracking() { | |
| trackStack.push(shouldTrack) | |
| shouldTrack = true | |
| } | |
| export function resetTracking() { | |
| const last = trackStack.pop() | |
| shouldTrack = last === undefined ? true : last | |
| } | 
如上,就是 effect 部分的源码。顺着执行顺序一步步走下来。
- 调用方调用 effect 函数,参数为函数 fn,options (默认为 {});
- 判断是否已经是 effect 过的函数,如果是的话,则直接把原函数返回。
- 调用 createReactiveEffect 生成当前 fn 对应的 effect 函数,把上面的参数 fn 和 options 直接传进去;
- 判断 options 里面的 lazy 是否是 false,如果不是懒处理,就直接调用下对应的 effect 函数;
- 返回生成的 effect 函数。
接下来看下 createReactiveEffect 函数的调用过程。
- 为 effect 函数赋值,暂时先不考虑 reactiveEffect 函数内部到底干了什么,只要明白创建了个函数,并赋值给了 effect 变量。
- 然后为 effect 函数添加属性:id, _isEffect, active, raw, deps, options
- 把 effect 返回了。
下面我们回到上面非 lazy 情况下,调用 effect,此时就会执行 reactiveEffect 函数。
- 首先判断了是否是 active 状态,如果不是,说明当前 effect 函数已经处于失效状态,直接返回 return options.scheduler ? undefined : fn()。
- 查看调用栈 effectStack 里面是否有当前 effect,如果无当前 effect,接着执行下面的代码。
- 先调用 cleanup,把当前所有依赖此 effect 的全部清掉,deps 是个数组,元素为 Set,Set 里面放的则是 ReactiveEffect,也就是 effect;
- 把当前 effect 入栈,并将当前 effect 置为当前活跃 effect->activeEffect;后执行 fn 函数;
- finally,把 effect 出栈,执行完成了,把 activeEffect 还原到之前的状态;
- 其中涉及到调用轨迹栈的记录。和 shouldTrack 是否需要跟踪轨迹的处理。
# stop
stop 方法是用来停止当前 effect 的。属于 Vue3 内部方法,来看下源码:
| export function stop(effect: ReactiveEffect) { | |
| if (effect.active) { | |
| cleanup(effect) | |
| if (effect.options.onStop) { | |
| effect.options.onStop() | |
|     } | |
| effect.active = false | |
|   } | |
| } | 
- 调用 cleanup 清空掉,和上面调用 cleanup 一样。
- 执行当前 effect.options.onnStop 钩子函数。
- 把当前 effect 的 active 状态置为 false。
# 结言
本篇文章主要围绕 reactivity 文件夹里面提供给大家使用的 compositionApi 的部分进行了相对应的使用和源码解读,大家感兴趣的还是去读下这部分的源码,毕竟这是 Vue3 新出的功能,越来越 react 的一步......
