最近有朋友在面試過(guò)程中經(jīng)常被問(wèn)到這么一個(gè)問(wèn)題,vue3 中的ref 和 reactive的區(qū)別在哪里,為什么 要定義兩個(gè)API 一個(gè) api不能實(shí)現(xiàn) 響應(yīng)式更新嗎??
帶著這個(gè)疑問(wèn) ,我們 接下來(lái)進(jìn)行逐一探討。
1 : 分析
1.1 ref and reactive 怎么用 ?
相信大家都知道在vue3中我們可以通過(guò)一些api去定義響應(yīng)式數(shù)據(jù),比如 ref, reactive ,shallowRef.
ref 基本使用
<template>
<div>
<span>{{inner.content.text}}</span>
<span>{{count}}</span>
</div>
</template>
<script setup>
const inner = ref({
content: {
text: "內(nèi)部?jī)?nèi)容"
}
}); // 你可以通過(guò) ref 定義復(fù)雜數(shù)據(jù)類型
// or
const count = ref(20); // 定義普通數(shù)據(jù)類型
</script>
reactive 基本使用
<template>
<div>
<div>{{wApper.subText}}</div>
<div v-for="item in list" :key="item.id">{{item.content}}</div>
</div>
</template>
<script setup>
const wapper = reactive({
subText: inner
});
const list = reactive([
{
id: 1,
content: "render"
},
{
id: 2,
content: "render2"
}
]);
</script>
當(dāng)然你還可以配合 computed od watchEffec使用 這里我就不再過(guò)多介紹了。
1.2 ref 和 reactive 的區(qū)別?
相信大家讀到這里可以看出 ref 既可以定義基本數(shù)據(jù)類型 也可以 定義復(fù)雜數(shù)據(jù)類型,而reactive只定義復(fù)雜數(shù)據(jù)類型。
那有人就問(wèn)了 ? reactive 只能存 復(fù)雜數(shù)據(jù)類型嗎?
答案很明顯不是的 reactive也可以存基本數(shù)據(jù)類型
那他們到底區(qū)別在哪里呢? 我想這個(gè)時(shí)候 從我們開發(fā)者的角度上沒(méi)辦法看出本質(zhì)的區(qū)別,無(wú)非是定義變量唄,那接下來(lái)請(qǐng)隨者我一起進(jìn)入源碼的是世界。
1.3 源碼實(shí)現(xiàn)流程 ?
1.3.1 如何找到源碼?
先回答第一個(gè)問(wèn)題,怎么找源碼,這個(gè)需要你對(duì)源碼的包非常熟悉 我們可以通過(guò)看package.json文件先找到它打包的入口文件,然后再去根據(jù)不同的情況找不同的文件。
1.3.2 : 找到ref函數(shù)的源碼文件 ,看看函數(shù)內(nèi)部做了什么事情?
源碼文件 : corepackagesreactivitysrcref.ts
ref.ts
核心代碼實(shí)現(xiàn)
// ref.ts 文件93 行
export function ref(value?: unknown) {
return createRef(value, false) //1 : 提供 ref函數(shù) , false 是否淺復(fù)制
}
// ref.ts文件第 127行
// 調(diào)用 ref 返回一個(gè) 創(chuàng)建 的方法 createRef 傳入 兩個(gè)值
/**
* @param rawValue ref函數(shù)傳入的參數(shù)
* @param shallow 是否淺復(fù)制
*/
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) { // 是否是ref對(duì)象 如果是 則 直接返回
return rawValue
}
return new RefImpl(rawValue, shallow) // 否則 創(chuàng)建 ref 對(duì)象 傳入 rawValue shallow
}
// ref.ts 文件第 134行
class RefImpl<T> { // 創(chuàng)建一個(gè) ref的 實(shí)現(xiàn)類
private _value: T // 創(chuàng)建私有的 _value 變量
private _rawValue: T // 創(chuàng)建私有的 _rawValue 變量
public dep?: Dep = undefined // 是否 dep
public readonly __v_isRef = true // 只讀的 屬性 是否是 ref
constructor(value: T, public readonly __v_isShallow: boolean) {
// 實(shí)例被 new時(shí) 執(zhí)行 constructor 保存 傳入的值
this._rawValue = __v_isShallow ? value : toRaw(value) // 是否淺復(fù)制 , 如果時(shí) 則直接返回 傳入的值 否則進(jìn)行 獲取其原始對(duì)象
this._value = __v_isShallow ? value : toReactive(value) // 是否淺復(fù)制 是 返回原value 否則 轉(zhuǎn)換成 reactive 對(duì)象
}
get value() { // 獲取值的時(shí)候 直接將 constructor 保存的值 返回
trackRefValue(this) // 跟蹤 ref 的 value
return this._value // 獲取value 是 返回 _value 對(duì)象
}
set value(newVal) {// 當(dāng) 設(shè)置值的時(shí)候 往下看
// 是否淺復(fù)制 or 值身上是否有 __v_isShallow 標(biāo)識(shí) or 是否是只讀的 標(biāo)識(shí)__v_isReadonly
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
// 如果滿足 則 返回新設(shè)置的值 , 如果不是 則 取出 新值的原始對(duì)象
newVal = useDirectValue ? newVal : toRaw(newVal) // 如果 你一個(gè) 淺層對(duì)象(普通數(shù)據(jù)類型) 則 原值返回 否則 判斷是否能從 代理對(duì)象中 取出源值
if (hasChanged(newVal, this._rawValue)) { // 判斷對(duì)象是否發(fā)生 變化 變了向下走
this._rawValue = newVal // 將最新值 賦給 _rawValue
this._value = useDirectValue ? newVal : toReactive(newVal) // 判斷是否是基本數(shù)據(jù)類型 如果 是 則 將最新值返回 否則 繼續(xù)轉(zhuǎn)換 reactive
triggerRefValue(this, newVal) // 觸發(fā) ref 的 value 值進(jìn)行監(jiān)聽(tīng)更新
}
}
}
// 判斷是否 是對(duì)象 如果是 則 reactive代理 否則 返回 當(dāng)前的value
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
以上代碼就是 ref 的核心實(shí)現(xiàn) , 相信看來(lái)好像源碼也沒(méi)有那么難。
1.3.3.總結(jié)一下 ref做了什么?
- 調(diào)用ref將 定義的數(shù)據(jù)傳入,返回一個(gè)創(chuàng)建ref響應(yīng)式數(shù)據(jù)的函數(shù),是否需要淺層復(fù)制,默認(rèn)為false,也就意味著一定會(huì)走 toReactive
- 調(diào)用createRef, 判斷是否 是一個(gè) ref對(duì)象 ,是 原值返回 否則 , new 一個(gè) 實(shí)現(xiàn)ref類
- 創(chuàng)建類的私有變量 ,保存?zhèn)魅氲膙alue 和 shallow
- 判斷是否淺層復(fù)制,如果是則 返回傳入的 value,否則取出 ref的原始值對(duì)象
- 獲取值的時(shí)候?qū)?保存的值 返回 出去
- 設(shè)置值的時(shí)候 判斷當(dāng)前屬性 是否是淺層對(duì)象 ,如果是 則返回該數(shù)據(jù) 否則 調(diào)用 toreactive轉(zhuǎn)換 reactive 進(jìn)行操作,如果是 普通數(shù)據(jù)類型,原值返回,按照 defineProperty 進(jìn)行處理
- 觸發(fā)更新
1.3.4 : 找到reactve函數(shù)的源碼文件 ,看看函數(shù)內(nèi)部做了什么事情?
reactive.ts
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) { // 如果是 只讀的 不允許 寫入 則返回只讀對(duì)象
return target
}
return createReactiveObject( // 返回一個(gè)創(chuàng)建 reactive 的對(duì)象
target, // 傳入 目標(biāo)對(duì)象
false, // 是否是只讀對(duì)象
mutableHandlers, //提供 get, set, deleteProperty, has, ownKeys 方法
mutableCollectionHandlers, // 太多了 自己看源碼
reactiveMap // 提供一個(gè) weakmap 集合
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
if (!isObject(target)) { // 如果不是一個(gè)對(duì)象 則 返回當(dāng)前 traget
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] && // 如果target 已經(jīng)是一個(gè) 代理對(duì)象 則 返回當(dāng)前對(duì)象
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target) // 如果對(duì)象已經(jīng)有了代理對(duì)象 則直接取值 返回
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( // 將對(duì)象進(jìn)行代理
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy) // 設(shè)置目標(biāo)為代理對(duì)象
return proxy // 將對(duì)象返回出去
}
1.3.5.總結(jié)一下 reactive做了什么?
- 調(diào)用reactive方法 傳數(shù)據(jù),判斷 對(duì)象是否是只讀對(duì)象 如果是 直接返回 無(wú)法操作 否則向下繼續(xù)執(zhí)行
- 調(diào)用 createReactiveObject 返回一個(gè)代理對(duì)象
- 判斷傳入的值是不是一個(gè)對(duì)象 如果不是則直接原路返回,否則判斷target 是不是一個(gè) 已經(jīng)代理過(guò)了的對(duì)象
- 如果是代理過(guò)的對(duì)象 原路返回 否則 判斷目標(biāo)對(duì)象是否有相應(yīng)代理有直接取出響應(yīng)代理對(duì)象 否則 繼續(xù)向下
- 針對(duì)不同的 值類型處理
- 代理整個(gè)trarget 我,將當(dāng)前代理的對(duì)象設(shè)置到weakmap 中 將代理完的對(duì)象返回
1.4 總結(jié)
從源碼的角度來(lái)說(shuō) ref本身 會(huì)判斷是否為 基本數(shù)據(jù)類型 如果是 則是defineProperty 實(shí)現(xiàn)的如果是復(fù)雜數(shù)據(jù)類型就會(huì)按照 reactive進(jìn)行處理 ,針對(duì)不同的數(shù)據(jù)類型會(huì)進(jìn)行操作,當(dāng)你ref為 對(duì)象時(shí)會(huì)轉(zhuǎn)換 reactive對(duì)象 將代理的對(duì)象 返回給 _value對(duì)象,如果是基本數(shù)據(jù)則會(huì)判斷是否需要淺層復(fù)制,不需要?jiǎng)t直接返回了。 而 reactive 這邊也會(huì)判斷 是不是 基本數(shù)據(jù)類型 是 直接返回 否則就直接將對(duì)象進(jìn)行了代理并返回。相信本篇文章能夠給你帶來(lái)一些啟發(fā)。
作者:前端小張同學(xué)
鏈接:
https://juejin.cn/post/7263411272892825655






