一步步从Vue3.x源码上理解ref和reactive的区别
阿远Carry 人气:0前言
对于 ref
和 reactive
, 应该说是只要用了Vue3.x
就会接触到
因为想要触发响应式,就必须通过 ref
和 reactive
来实现
但,对于他们理解,大家也是众说纷纭
那本文将从源码层面,带你去理解一下 ref
和 reactive
的区别
⚠️ 此文章基于 Vue 3.2.47 进行分析
使用
ref
可以使用 基本对象 和 引用类型 对象,如:
ref({ num: 1 }) ref(1)
而,reactive
只能使用 引用类型
reactive({ num: 1 })
原理
ref
从源码上看,ref
方法,就是返回 createRef
的函数调用
// 位置在 /core-3.2.47/packages/reactivity/src/ref.ts 第 82 行 export function ref(value?: unknown) { return createRef(value, false) }
而createRef
方法就是创建 RefImpl
对象的实例
// 位置在 /core-3.2.47/packages/reactivity/src/ref.ts 第 99 行 function createRef(rawValue: unknown, shallow: boolean) { if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, shallow) }
那么,很好理解了,为什么我们打印 ref(1)
会是这样
那,RefImpl
对象的功能是什么呢?
首先来看 constructor
构造函数
// 位置在 /core-3.2.47/packages/reactivity/src/ref.ts 第 106 行 class RefImpl<T> { private _value: T private _rawValue: T public dep?: Dep = undefined public readonly __v_isRef = true constructor(value: T, public readonly __v_isShallow: boolean) { this._rawValue = __v_isShallow ? value : toRaw(value) this._value = __v_isShallow ? value : toReactive(value) } // 省略部分代码... }
在创建 RefImpl
的实例过程中, 由于在 ref
函数中调用 createRef
传入第二参数为 false
,可以直接理解为
this._rawValue = toRaw(value) this._value = toReactive(value)
toRaw
在递归中检查对象上是否有 __v_raw
,可以理解为是返回原始数据
// 位置在 /core-3.2.47/packages/reactivity/src/reactive.ts 第 239 行 export function toRaw<T>(observed: T): T { const raw = observed && (observed as Target)[ReactiveFlags.RAW] return raw ? toRaw(raw) : observed } // 位置在 /core-3.2.47/packages/reactivity/src/reactive.ts 第 16 行 export const enum ReactiveFlags { SKIP = '__v_skip', IS_REACTIVE = '__v_isReactive', IS_READONLY = '__v_isReadonly', IS_SHALLOW = '__v_isShallow', RAW = '__v_raw' }
toReactive
判断如果他是引用类型
的对象,那就使用 reactive
返回对象,如果不是,那就原值返回
// 位置在 /core-3.2.47/packages/reactivity/src/reactive.ts 第 251 行 export const toReactive = <T extends unknown>(value: T): T => isObject(value) ? reactive(value) : value // 位置在 /core-3.2.47/packages/shared/src/index.ts 第 63 行 export const isObject = (val: unknown): val is Record<any, any> => val !== null && typeof val === 'object'
至此我们就是知道了,ref
如果传入 引用类型
的对象底层还是调用 reactive
但是乍一想,好像不对?那 ref
如何进行做响应式的呢?
其实原理在
// 位置在 /core-3.2.47/packages/reactivity/src/ref.ts 第 118 行 get value() { trackRefValue(this) return this._value } set value(newVal) { const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal) newVal = useDirectValue ? newVal : toRaw(newVal) if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) triggerRefValue(this, newVal) } }
我们都知道,如果想要触发 ref
的值更新,必须使用 .value
,例如:
const num = ref(1) console.log(num.value) num.value = 2
我们知道,所谓的响应式其实就是依赖收集
和派发更新
的过程
对于 console.log(num.value)
我们会触发 get value
函数,进行依赖收集
对于 num.value = 2
我们会触发 set value
函数,进行派发更新
所以
- ref 对于简单类型是通过 get value 和 set value 进行依赖收集和派发更新
- ref 对于引用类型是通过 reactive 进行依赖收集和派发更新
但,我们依旧需要注意:
const count = ref({ num: 1 }) count.value.num = 2 // 不会触发 set value
reactive
从源码上看,reactive
方法,就是返回 createReactiveObject
的函数调用
// 位置在 /core-3.2.47/packages/reactivity/src/reactive.ts 第 90 行 export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. if (isReadonly(target)) { return target } return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap ) }
而 createReactiveObject
方法,使用了 proxy
对值创建的代理对象,并返回代理对象
// 位置在 /core-3.2.47/packages/reactivity/src/reactive.ts 第 181 行 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 specific value types can be observed. 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 } // 位置在 /core-3.2.47/packages/reactivity/src/reactive.ts 第 58 行 function getTargetType(value: Target) { return value[ReactiveFlags.SKIP] || !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value)) } // 位置在 /core-3.2.47/packages/reactivity/src/reactive.ts 第 43 行 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 } } // 位置在 /core-3.2.47/packages/reactivity/src/reactive.ts 第 37 行 const enum TargetType { INVALID = 0, COMMON = 1, COLLECTION = 2 }
现在,我们应该聚焦一下 baseHandlers
,因为根据运行上下文可以知道,我们当前的 targetType
为 1
, 所以传入baseHandlers
对象
而 baseHandlers
是从createReactiveObject
进行传入,也就是 mutableHandlers
// 位置在 /core-3.2.47/packages/reactivity/src/baseHandlers.ts 第 225 行 export const mutableHandlers: ProxyHandler<object> = { get, set, deleteProperty, has, ownKeys } // 位置在 /core-3.2.47/packages/reactivity/src/baseHandlers.ts 第 48 行 const get = /*#__PURE__*/ createGetter() // 位置在 /core-3.2.47/packages/reactivity/src/baseHandlers.ts 第 158 行 const set = /*#__PURE__*/ createSetter()
get
是通过 createGetter
方法创建
set
是通过 createGetter
方法创建
对于 createGetter
返回了一个 get
函数
// 位置在 /core-3.2.47/packages/reactivity/src/baseHandlers.ts 第 94 行 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 } else if ( key === ReactiveFlags.RAW && receiver === (isReadonly ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap ).get(target) ) { return target } const targetIsArray = isArray(target) if (!isReadonly) { if (targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect.get(arrayInstrumentations, key, receiver) } if (key === 'hasOwnProperty') { return hasOwnProperty } } 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 (shallow) { return res } if (isRef(res)) { // ref unwrapping - skip unwrap for Array + integer key. return targetIsArray && isIntegerKey(key) ? res : res.value } if (isObject(res)) { // Convert returned value into a proxy as well. we do the isObject check // here to avoid invalid value warning. Also need to lazy access readonly // and reactive here to avoid circular dependency. return isReadonly ? readonly(res) : reactive(res) } return res } }
对于 createGetter
返回了一个 set
函数
// 位置在 /core-3.2.47/packages/reactivity/src/baseHandlers.ts 第 161 行 function createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { let oldValue = (target as any)[key] if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) { return false } if (!shallow) { if (!isShallow(value) && !isReadonly(value)) { oldValue = toRaw(oldValue) value = toRaw(value) } 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 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 } }
其说白了,reactive
依赖 Proxy
将值作为被代理对象,创建代理对象,也是通过get
和set
,进行依赖收集
和派发更新
此时,我们也能理解了打印reactive({num: 1})
为什么是Proxy
对象
总结
ref
其实就是创建RefImpl
的实例对象,对于 简单类型 直接通过get value
和set value
进行依赖收集和派发更新 ,而对于引用类型
直接调用reactive
方法reactive
底层用了Proxy
对象,创建出代理对象,进行依赖收集和派发更新
加载全部内容