一步步从Vue3.x源码上理解ref和reactive的区别
作者:阿远Carry
前言
对于 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
对象,创建出代理对象,进行依赖收集和派发更新
到此这篇关于一步步从Vue3.x源码上理解ref和reactive区别的文章就介绍到这了,更多相关Vue3.x ref和reactive的区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!