分析的是 @vue/composition-api 的代码,版本是 V1.0.0-rc.1 vue 的版本是 2.6.12
因为对于 reactive 和 ref 各自的行为和用法尚有不明之处,所以看一下源码,记录一下笔记
文件结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ├─scripts ├─src │ ├─apis │ ├─component │ ├─reactivity │ ├─types │ └─utils ├─test │ ├─apis │ ├─helpers │ ├─ssr │ ├─types │ └─v3 │ ├─reactivity │ └─runtime-core └─test-dts
这次主要关注 src/reactivity 和 src/apis 中的代码
reactivity/reactive.ts reactive reactive 和 ref 相关的方法在 reactivity 目录下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 export function reactive <T extends object >(obj: T ): UnwrapRef <T > { if ( !(isPlainObject(obj) || isArray(obj)) || isRaw(obj) || !Object .isExtensible(obj) ) { return obj as any ; } const observed = observe(obj); setupAccessControl(observed); return observed as UnwrapRef<T>; }
然后是看 observe 是啥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function observe (obj ) { var Vue = getRegisteredVueOrDefault(); var observed; if (Vue.observable) { observed = Vue.observable(obj); } else { var vm = defineComponentInstance(Vue, { data : { $$state : obj, }, }); observed = vm._data.$$state; } if (!hasOwn(observed, "__ob__" )) { def(observed, "__ob__" , mockObserver(observed)); } return observed; }
如果 Vue 上有 Vue.observe,使用 Vue.observe
1 2 3 4 5 Vue.observable = function (obj ) { observe(obj); return obj; };
Vue.observe 直接返回了传入的对象,也就是说,reactive 的作用只是给对象增加响应式,返回的值是对象本身
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function observe (value, asRootData ) { if (!isObject(value) || value instanceof VNode) { return ; } var ob; if (hasOwn(value, "__ob__" ) && value.__ob__ instanceof Observer) { ob = value.__ob__; } else if ( shouldObserve && !isServerRendering() && (Array .isArray(value) || isPlainObject(value)) && Object .isExtensible(value) && !value._isVue ) { ob = new Observer(value); } if (asRootData && ob) { ob.vmCount++; } return ob; }
如果之前没有被 observe 过,使用 new Observer(value) 添加响应式属性,否则什么都不做
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var Observer = function Observer (value ) { this .value = value; this .dep = new Dep(); this .vmCount = 0 ; def(value, "__ob__" , this ); if (Array .isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } this .observeArray(value); } else { this .walk(value); } };
可以看到这个构造方法给对象增加了 __ob__
属性。
那么看以下代码
1 2 3 4 5 let a = { a : 100 , ); let b = reactive(a);reactive(b)
这么使用 reactive 是没问题的,同时 a,b 这里指向的时同一个响应式对象,reactive(b) 这句实际上没有任何作用
显然我们不能重新给 a 或 b 赋值,因为响应性是赋予在 a,b 指向的对象上的
然后看下 setupAccessControl 干了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function setupAccessControl (target: AnyObject ): void { if ( !isPlainObject(target) || isRaw(target) || Array .isArray(target) || isRef(target) || isComponentInstance(target) || accessModifiedSet.has(target) ) return ; accessModifiedSet.set(target, true ); const keys = Object .keys(target); for (let i = 0 ; i < keys.length; i++) { defineAccessControl(target, keys[i]); } }
accessModifiedSet 是一个 weakmap,如果之前对对象设置过,就不再设置了 对对象的每一个属性调用 defineAccessControl
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 export function defineAccessControl (target: AnyObject, key: any , val?: any ) { if (key === "__ob__" ) return ; if (isRaw(target[key])) return ; let getter: (() => any ) | undefined ; let setter: ((x: any ) => void ) | undefined ; const property = Object .getOwnPropertyDescriptor(target, key); if (property) { if (property.configurable === false ) { return ; } getter = property.get; setter = property.set; if ( (!getter || setter) && arguments .length === 2 ) { val = target[key]; } } setupAccessControl(val); Object .defineProperty(target, key, { enumerable : true , configurable : true , get : function getterHandler ( ) { const value = getter ? getter.call(target) : val; if (key !== RefKey && isRef(value)) { return value.value; } else { return value; } }, set : function setterHandler (newVal ) { if (getter && !setter) return ; const value = getter ? getter.call(target) : val; if (key !== RefKey && isRef(value) && !isRef(newVal)) { value.value = newVal; } else if (setter) { setter.call(target, newVal); } else { val = newVal; } setupAccessControl(newVal); }, }); } function setupAccessControl (target: AnyObject ): void { if ( !isPlainObject(target) || isRaw(target) || Array .isArray(target) || isRef(target) || isComponentInstance(target) || accessModifiedSet.has(target) ) return ; accessModifiedSet.set(target, true ); const keys = Object .keys(target); for (let i = 0 ; i < keys.length; i++) { defineAccessControl(target, keys[i]); } }
如果 val 是一个对象 递归调用 defineAccessControl defineAccessControl 的作用是 reactive 对象在读写属性时,如果属性是一个 Ref 对象,可以自动拆装箱
shallowReactive 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 export function shallowReactive (obj: any ): any { if ( !(isPlainObject(obj) || isArray(obj)) || isRaw(obj) || !Object .isExtensible(obj) ) { return obj as any ; } const observed = observe({}); setupAccessControl(observed); const ob = (observed as any ).__ob__; for (const key of Object .keys(obj)) { let val = obj[key]; let getter: (() => any ) | undefined ; let setter: ((x: any ) => void ) | undefined ; const property = Object .getOwnPropertyDescriptor(obj, key); if (property) { if (property.configurable === false ) { continue ; } getter = property.get; setter = property.set; } Object .defineProperty(observed, key, { enumerable : true , configurable : true , get : function getterHandler ( ) { const value = getter ? getter.call(obj) : val; ob.dep?.depend(); return value; }, set : function setterHandler (newVal ) { if (getter && !setter) return ; if (setter) { setter.call(obj, newVal); } else { val = newVal; } ob.dep?.notify(); }, }); } return observed; }
只对响应式对象的属性手动设置响应式并管理依赖收集,如果属性是对象,不会深度添加响应式
isReactive 1 2 3 4 export function isReactive (obj: any ): boolean { return Boolean (obj?.__ob__ && !obj.__ob__?.__raw__); }
就是看对象是否有 __ob__
属性并且没有使用 markRaw 标记
markRaw && isRaw && toRaw 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 markRaw <T extends object >(obj: T ): T { if (!(isPlainObject(obj) || isArray(obj)) || !Object .isExtensible(obj)) { return obj; } const ob = createObserver(); ob.__raw__ = true ; def(obj, "__ob__" , ob); rawSet.set(obj, true ); return obj; } export function isRaw (obj: any ): boolean { return Boolean (obj?.__ob__ && obj.__ob__?.__raw__); } export function toRaw <T >(observed: T ): T { if (isRaw(observed) || !Object .isExtensible(observed)) { return observed; } return (observed as any )?.__ob__?.value || observed; }
markRaw 给目标添加了 vue observable 的 flag,这样 Vue 应该就不会再给对象添加响应式属性了 rawSet 也是一个 weakmap isRaw 通过 markRaw 设置的 __ob__.__raw__
flag 来判断 toRaw 是获取响应式对象的原本对象 这里 和 vue3 实现的有些差异,通过之前我们知道 reactive 是在原对象上增加响应式属性__ob__
的,而 vue3 是通过 proxy 实现的,所以 toRaw 拿到的是被代理的原始对象,而这里这个 toRaw 有点意义不明
reactivity/ref.ts 这个文件上来就是一堆类型定义,看的我人晕掉了。。
ref && isRef 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export const RefKey = "composition-api.refKey" ;export function ref <T extends object >( raw: T ): T extends Ref ? T : Ref <UnwrapRef <T >> ;export function ref <T >(raw: T ): Ref <UnwrapRef <T >> ;export function ref <T = any >( ): Ref <T | undefined > ;export function ref (raw?: unknown ) { if (isRef(raw)) { return raw; } const value = reactive({ [RefKey]: raw }); return createRef({ get : () => value[RefKey] as any , set : (v ) => ((value[RefKey] as any ) = v), }); }
好家伙,上来就是三个重载,然后 Ref<UnwrapRef<T>>
这玩意好绕,先不管了 如果传入的对象已经是 Ref 对象,直接返回,否则通过 reactive 创建一个响应式对象,然后返回通过 createRef 创建的对象
1 2 3 4 export function isRef <T >(value: any ): value is Ref <T > { return value instanceof RefImpl; }
通过 instanceOf 判断是否是 Ref 对象是
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 export function createRef <T >(options: RefOption<T>, readonly = false ) { const r = new RefImpl<T>(options); const sealed = Object .seal(r); readonlySet.set(sealed, true ); return sealed; } class RefImpl <T > implements Ref <T > { readonly [_refBrand]!: true ; public value!: T; constructor ({ get, set }: RefOption<T> ) { proxy(this , "value" , { get, set, }); } } const sharedPropertyDefinition = { enumerable : true , configurable : true , get : noopFn, set : noopFn, }; export function proxy ( target: any , key: string , { get, set }: { get?: Function ; set?: Function } ) { sharedPropertyDefinition.get = get || noopFn; sharedPropertyDefinition.set = set || noopFn; Object .defineProperty(target, key, sharedPropertyDefinition); }
createRef 就是创建一个 RefImpl 对象,然后代理了其中 value 属性的 getter 和 setter Object.seal 让一个对象密封,并返回被密封后的对象。密封对象是指那些不能添加新的属性,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性,但可以修改已有属性的值的对象。
toRef && toRefs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 export function toRef <T extends object , K extends keyof T >( object : T, key: K ): Ref <T [K ]> { const v = object [key]; if (isRef<T[K]>(v)) return v; return createRef({ get : () => object [key], set : (v ) => (object [key] = v), }); } export function toRefs <T extends Data = Data >(obj: T ): ToRefs <T > { if (!isPlainObject(obj)) return obj as any ; const ret: any = {}; for (const key in obj) { ret[key] = toRef(obj, key); } return ret; }
把对象的某个属性变成 Ref 类型 如果已经是 Ref 类型了,直接返回,否则返回通过 createRef 创建的对象 需要注意传入的 object 必须是一个响应式对象,否则返回的 Ref 是不具有响应式特性的,因为无法进行依赖收集相关的操作。
unref 1 2 3 4 export function unref <T >(ref: T ): T extends Ref <infer V > ? V : T { return isRef(ref) ? (ref.value as any ) : ref; }
返回 Ref 的 value 属性
shallowRef 1 2 3 4 5 6 7 8 9 10 11 export function shallowRef (raw?: unknown ) { if (isRef(raw)) { return raw; } const value = shallowReactive({ [RefKey]: raw }); return createRef({ get : () => value[RefKey] as any , set : (v ) => ((value[RefKey] as any ) = v), }); }
很好理解,这里的 reactive 对象是通过 shallowReactive 创建的
customRef 1 2 3 4 5 6 7 8 9 10 11 12 export function customRef <T >(factory: CustomRefFactory<T> ): Ref <T > { const version = ref(0 ); return createRef( factory( () => void version.value, () => { ++version.value; } ) ); }
允许传入一个工厂方法,属于比较高级的应用,略过
triggerRef 1 2 3 4 5 6 export function triggerRef (value: any ) { if (!isRef(value)) return ; value.value = value.value; }
vue3 文档上的例子,手动触发响应式侦测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const shallow = shallowRef({ greet : "Hello, world" , }); watchEffect(() => { console .log(shallow.value.greet); }); shallow.value.greet = "Hello, universe" ; triggerRef(shallow);
proxyRefs 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 proxyRefs <T extends object >( objectWithRefs: T ): ShallowUnwrapRef <T > { if (isReactive(objectWithRefs)) { return objectWithRefs as ShallowUnwrapRef<T>; } const value: Record<string , any > = reactive({ [RefKey]: objectWithRefs }); for (const key of Object .keys(objectWithRefs)) { proxy(value, key, { get ( ) { if (isRef(value[key])) { return value[key].value; } return value[key]; }, set (v: unknown ) { if (isRef(value[key])) { return (value[key].value = unref(v)); } value[key] = unref(v); }, }); } return value as ShallowUnwrapRef<T>; }
看了一下,是 vue3 的新特性方法,这里也跟进了 如果 objectWithRefs 的属性是 Ref 的话,自动进行拆装箱