一步步從Vue3.x源碼上理解ref和reactive的區(qū)別
前言
對于 ref
和 reactive
, 應該說是只要用了Vue3.x
就會接觸到
因為想要觸發(fā)響應式,就必須通過 ref
和 reactive
來實現
但,對于他們理解,大家也是眾說紛紜
那本文將從源碼層面,帶你去理解一下 ref
和 reactive
的區(qū)別
?? 此文章基于 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
方法就是創(chuàng)建 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) } // 省略部分代碼... }
在創(chuàng)建 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) } }
我們都知道,如果想要觸發(fā) ref
的值更新,必須使用 .value
,例如:
const num = ref(1) console.log(num.value) num.value = 2
我們知道,所謂的響應式其實就是依賴收集
和派發(fā)更新
的過程
對于 console.log(num.value)
我們會觸發(fā) get value
函數,進行依賴收集
對于 num.value = 2
我們會觸發(fā) set value
函數,進行派發(fā)更新
所以
- ref 對于簡單類型是通過 get value 和 set value 進行依賴收集和派發(fā)更新
- ref 對于引用類型是通過 reactive 進行依賴收集和派發(fā)更新
但,我們依舊需要注意:
const count = ref({ num: 1 }) count.value.num = 2 // 不會觸發(fā) 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
對值創(chuàng)建的代理對象,并返回代理對象
// 位置在 /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
方法創(chuàng)建
set
是通過 createGetter
方法創(chuàng)建
對于 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
將值作為被代理對象,創(chuàng)建代理對象,也是通過get
和set
,進行依賴收集
和派發(fā)更新
此時,我們也能理解了打印reactive({num: 1})
為什么是Proxy
對象
總結
ref
其實就是創(chuàng)建RefImpl
的實例對象,對于 簡單類型 直接通過get value
和set value
進行依賴收集和派發(fā)更新 ,而對于引用類型
直接調用reactive
方法reactive
底層用了Proxy
對象,創(chuàng)建出代理對象,進行依賴收集和派發(fā)更新
到此這篇關于一步步從Vue3.x源碼上理解ref和reactive區(qū)別的文章就介紹到這了,更多相關Vue3.x ref和reactive的區(qū)別內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Nuxt pages下不同的頁面對應layout下的頁面布局操作
這篇文章主要介紹了Nuxt pages下不同的頁面對應layout下的頁面布局操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11vue-create創(chuàng)建VUE3項目詳細圖文教程
create-vue是Vue官方新的腳手架工具,底層切換到了vite(下一代前端工具鏈),為開發(fā)提供極速響應,下面這篇文章主要給大家介紹了關于vue-create創(chuàng)建VUE3項目的相關資料,需要的朋友可以參考下2024-03-03