一步步從Vue3.x源碼上理解ref和reactive的區(qū)別
前言
對于 ref 和 reactive, 應(yīng)該說是只要用了Vue3.x就會接觸到
因為想要觸發(fā)響應(yīng)式,就必須通過 ref 和 reactive 來實現(xiàn)
但,對于他們理解,大家也是眾說紛紜
那本文將從源碼層面,帶你去理解一下 ref 和 reactive 的區(qū)別
?? 此文章基于 Vue 3.2.47 進行分析
使用
ref 可以使用 基本對象 和 引用類型 對象,如:
ref({ num: 1 })
ref(1)而,reactive 只能使用 引用類型
reactive({ num: 1 })原理
ref
從源碼上看,ref 方法,就是返回 createRef 的函數(shù)調(diào)用
// 位置在 /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 構(gòu)造函數(shù)
// 位置在 /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 函數(shù)中調(diào)用 createRef 傳入第二參數(shù)為 false,可以直接理解為
this._rawValue = toRaw(value) this._value = toReactive(value)
toRaw 在遞歸中檢查對象上是否有 __v_raw,可以理解為是返回原始數(shù)據(jù)
// 位置在 /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 如果傳入 引用類型的對象底層還是調(diào)用 reactive
但是乍一想,好像不對?那 ref 如何進行做響應(yīng)式的呢?
其實原理在
// 位置在 /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
我們知道,所謂的響應(yīng)式其實就是依賴收集和派發(fā)更新的過程
對于 console.log(num.value) 我們會觸發(fā) get value 函數(shù),進行依賴收集
對于 num.value = 2 我們會觸發(fā) set value 函數(shù),進行派發(fā)更新
所以
- ref 對于簡單類型是通過 get value 和 set value 進行依賴收集和派發(fā)更新
- ref 對于引用類型是通過 reactive 進行依賴收集和派發(fā)更新
但,我們依舊需要注意:
const count = ref({ num: 1 })
count.value.num = 2 // 不會觸發(fā) set valuereactive
從源碼上看,reactive 方法,就是返回 createReactiveObject 的函數(shù)調(diào)用
// 位置在 /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
}現(xiàn)在,我們應(yīng)該聚焦一下 baseHandlers,因為根據(jù)運行上下文可以知道,我們當(dāng)前的 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 函數(shù)
// 位置在 /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 函數(shù)
// 位置在 /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 對象

總結(jié)
ref其實就是創(chuàng)建RefImpl的實例對象,對于 簡單類型 直接通過get value和set value進行依賴收集和派發(fā)更新 ,而對于引用類型直接調(diào)用reactive方法reactive底層用了Proxy對象,創(chuàng)建出代理對象,進行依賴收集和派發(fā)更新
到此這篇關(guān)于一步步從Vue3.x源碼上理解ref和reactive區(qū)別的文章就介紹到這了,更多相關(guān)Vue3.x ref和reactive的區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Nuxt pages下不同的頁面對應(yīng)layout下的頁面布局操作
這篇文章主要介紹了Nuxt pages下不同的頁面對應(yīng)layout下的頁面布局操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
vue 實現(xiàn)一個簡單的全局調(diào)用彈窗案例
這篇文章主要介紹了vue 實現(xiàn)一個簡單的全局調(diào)用彈窗案例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
vue如何實現(xiàn)動態(tài)改變地址欄的參數(shù)值
這篇文章主要介紹了vue如何實現(xiàn)動態(tài)改變地址欄的參數(shù)值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
vue實現(xiàn)element表格里表頭信息提示功能(推薦)
小編最近接了這樣一個需求,需要在element表格操作一欄添加提示功能,下面小編給大家?guī)砹嘶趘ue實現(xiàn)element表格里表頭信息提示功能,需要的朋友參考下吧2019-11-11
vue-create創(chuàng)建VUE3項目詳細圖文教程
create-vue是Vue官方新的腳手架工具,底層切換到了vite(下一代前端工具鏈),為開發(fā)提供極速響應(yīng),下面這篇文章主要給大家介紹了關(guān)于vue-create創(chuàng)建VUE3項目的相關(guān)資料,需要的朋友可以參考下2024-03-03
vue中回調(diào)函數(shù)(callback)的用法舉例
這篇文章主要給大家介紹了關(guān)于vue中回調(diào)函數(shù)(callback)的用法舉例,所謂的回調(diào)函數(shù),就是由調(diào)用函數(shù)提供執(zhí)行代碼,被調(diào)用函數(shù)執(zhí)行完畢之后,再自動執(zhí)行的一個函數(shù),需要的朋友可以參考下2023-08-08

