深入源碼理解Vue3 reactive

我們先來(lái)看官方對(duì)于reactive的解釋?zhuān)俜降慕忉屢卜浅:?jiǎn)單

返回對(duì)象的響應(yīng)式副本

但從這句話(huà)我們可以得到以下信息

  1. reactive接受一個(gè)對(duì)象作為參數(shù)
  2. 其返回值是經(jīng)reactive函數(shù)包裝過(guò)后的數(shù)據(jù)對(duì)象,這個(gè)對(duì)象具有響應(yīng)式

但同樣會(huì)有一些疑問(wèn)
比如,reactive的參數(shù)只能傳遞一個(gè)對(duì)象嗎,如果傳遞其他值會(huì)怎么樣?
比如,返回的響應(yīng)式數(shù)據(jù)的本質(zhì)是什么,為啥就能讓數(shù)據(jù)變成響應(yīng)式?
比如,"副本"是不是意味著響應(yīng)式數(shù)據(jù)與原始數(shù)據(jù)沒(méi)有關(guān)聯(lián)?
比如,返回的響應(yīng)式副本里頭的數(shù)據(jù)是深度響應(yīng)式嗎,即是否遞歸監(jiān)聽(tīng)對(duì)象的所有屬性?等等

帶著這些疑問(wèn)我們一起來(lái)看
首先,通過(guò)reactive創(chuàng)建一個(gè)響應(yīng)數(shù)據(jù)

import { reactive } from "vue";
export default {
  setup() {  
    const state = reactive({
      count: 0,
    });
  },
};

如上代碼就可以創(chuàng)建一個(gè)響應(yīng)式數(shù)據(jù)state,我具體來(lái)看一下這個(gè)

console.log(state)

可以看見(jiàn),返回的響應(yīng)副本state其實(shí)就是Proxy對(duì)象。所以reactive實(shí)現(xiàn)響應(yīng)式就是基于ES2015 Proxy的實(shí)現(xiàn)的。那我們知道Proxy有幾個(gè)特點(diǎn):

  1. 代理的對(duì)象是不等于原始數(shù)據(jù)對(duì)象
  2. 原始對(duì)象里頭的數(shù)據(jù)和被Proxy包裝的對(duì)象之間是有關(guān)聯(lián)的。即當(dāng)原始對(duì)象里頭數(shù)據(jù)發(fā)生改變時(shí),會(huì)影響代理對(duì)象;代理對(duì)象里頭的數(shù)據(jù)發(fā)生變化對(duì)應(yīng)的原始數(shù)據(jù)也會(huì)發(fā)生變化。
    需要記住:是對(duì)象里頭的數(shù)據(jù)變化,并不能將原始變量的重新賦值,那是大換血了

因此,既然reactive實(shí)現(xiàn)響應(yīng)式是基于Proxy的實(shí)現(xiàn)的,那我們大膽猜測(cè),原始數(shù)據(jù)與相應(yīng)數(shù)據(jù)也是有關(guān)聯(lián)的。那我們來(lái)測(cè)試一下

<template>
  <button @click="change">
    {{ state.count }}
  </button>
</template>
<script>
import { reactive } from "vue";
export default {
  setup() {
    const obj = {
      count: 0,
    };
    const state = reactive(obj);
    function change(){
        ++state.count
        console.log(obj);
        console.log(state);
    }
    return { state,change};
  },
};
</script>

以上代碼測(cè)試結(jié)果如下

驗(yàn)證,確實(shí)當(dāng)響應(yīng)式對(duì)象里頭數(shù)據(jù)變化的時(shí)候原始對(duì)象的數(shù)據(jù)也會(huì)變化
如果反過(guò)來(lái),結(jié)果也是一樣

 // ++state.count
++obj.count;

當(dāng)響應(yīng)式對(duì)象里頭數(shù)據(jù)變化的時(shí)候原始對(duì)象的數(shù)據(jù)也會(huì)變化
那問(wèn)題來(lái)了,我們操作數(shù)據(jù)的時(shí)候通過(guò)誰(shuí)來(lái)操作呢?
官方的建議是

建議只使用響應(yīng)式代理,避免依賴(lài)原始對(duì)象

再來(lái)解決另外一個(gè)問(wèn)題看看reactive是否會(huì)深度監(jiān)聽(tīng)每一層呢?

const state = reactive({
    a:{
        b:{
            c:{name:'c'}
        }
    }
});    
console.log(state);  
console.log(state.a);
console.log(state.a.b);  
console.log(state.a.b.c); 

可以看到結(jié)果reactive是遞歸會(huì)將每一層包裝成Proxy對(duì)象的,深度監(jiān)聽(tīng)每一層的property

最后測(cè)試一下如果reactive傳遞是非對(duì)象而是原始值會(huì)怎么樣

const state = reactive(0);  
console.log(state)

結(jié)果是,原始值并不會(huì)被包裝,所以也沒(méi)有響應(yīng)式特點(diǎn)

下面,我們看看reactive的源碼吧
源碼目錄位置:vue-next\packages\reactivity\src\reactive.ts
直接找到reactive的類(lèi)型聲明:

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>

可以看到reactive接受一個(gè)參數(shù)target,target的類(lèi)型是泛型T,而T類(lèi)型是extends object,簡(jiǎn)單來(lái)說(shuō)接受的參數(shù)target的類(lèi)型是object類(lèi)型或者時(shí)繼承自object類(lèi)的子類(lèi)類(lèi)型
返回值的類(lèi)型的UnwrapNestedRefs<T>
看看UnwrapNestedRefs<T>類(lèi)型

type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>

使用type關(guān)鍵字聲明類(lèi)型UnwrapNestedRefs<T>,這里有個(gè)三目運(yùn)算符,用于進(jìn)一步判斷T;如果傳入的T屬于Refs類(lèi)或者其子類(lèi),那么返回傳入的T,否者就是UnwrapRef<T>

下面具體看看reactive方法的定義

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

接受一個(gè)類(lèi)型為object參數(shù),當(dāng)傳入對(duì)象是只讀,返回本身。這里的as關(guān)鍵字是斷言,表示傳入的值一定是Target類(lèi)型,里頭有個(gè)ReactiveFlags.IS_READONLY,用于判斷是否是只讀的屬性

export interface Target {
  [ReactiveFlags.SKIP]?: boolean
  [ReactiveFlags.IS_REACTIVE]?: boolean
  [ReactiveFlags.IS_READONLY]?: boolean
  [ReactiveFlags.RAW]?: any
}

如果傳遞的對(duì)象是普通對(duì)象(不是readonly),則執(zhí)行創(chuàng)建響應(yīng)式對(duì)象函數(shù)createReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers)
該方法比較長(zhǎng),是reactive的核心方法,所以還是得讀一下源碼

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<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 proxyMap = isReadonly ? readonlyMap : reactiveMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of 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
}

可以看到除了幾種特殊情況返回target本身之外,就返回proxy,proxy就是通過(guò)new Proxy構(gòu)造函數(shù)構(gòu)建出來(lái)的。這里也進(jìn)一步證明了reactive的響應(yīng)式功能確實(shí)是通過(guò)Proxy實(shí)現(xiàn)的
可以看一樣Proxy的定義

interface ProxyHandler<T extends object> {
    getPrototypeOf? (target: T): object | null;
    setPrototypeOf? (target: T, v: any): boolean;
    isExtensible? (target: T): boolean;
    preventExtensions? (target: T): boolean;
    getOwnPropertyDescriptor? (target: T, p: PropertyKey): PropertyDescriptor | undefined;
    has? (target: T, p: PropertyKey): boolean;
    get? (target: T, p: PropertyKey, receiver: any): any;
    set? (target: T, p: PropertyKey, value: any, receiver: any): boolean;
    deleteProperty? (target: T, p: PropertyKey): boolean;
    defineProperty? (target: T, p: PropertyKey, attributes: PropertyDescriptor): boolean;
    enumerate? (target: T): PropertyKey[];
    ownKeys? (target: T): PropertyKey[];
    apply? (target: T, thisArg: any, argArray?: any): any;
    construct? (target: T, argArray: any, newTarget?: any): object;
}
interface ProxyConstructor {
    revocable<T extends object>(target: T, handler: ProxyHandler<T>): { proxy: T; revoke: () => void; };
    new <T extends object>(target: T, handler: ProxyHandler<T>): T;
}
declare var Proxy: ProxyConstructor;

里面的具體實(shí)現(xiàn)方法,在createReactiveObject傳參的時(shí)候就傳入進(jìn)來(lái)了
mutableHandlers和mutableCollectionHandlers,具體可以去`vue-next\packages\reactivity\src\baseHandlers.ts文件中看

經(jīng)過(guò)上面的了解,我們可以總結(jié)和回答一下最開(kāi)始幾個(gè)疑問(wèn)了
1. reactive的參數(shù)可以傳遞對(duì)象也可以傳遞原始值。但是原始值并不會(huì)包裝成響應(yīng)式數(shù)據(jù)
2. 返回的響應(yīng)式數(shù)據(jù)的本質(zhì)Proxy對(duì)象
3. 返回的響應(yīng)式"副本"與原始數(shù)據(jù)有關(guān)聯(lián),當(dāng)原始對(duì)象里頭的數(shù)據(jù)或者響應(yīng)式對(duì)象里頭的數(shù)據(jù)發(fā)生,會(huì)彼此相互影響。兩種都可以觸發(fā)界面更新,操作時(shí)建議只使用響應(yīng)式代理對(duì)象
4. 返回的響應(yīng)式對(duì)象里頭時(shí)深度遞歸監(jiān)聽(tīng)每一層的,每一層都會(huì)被包裝成Proxy對(duì)象

以上就是Vue3中reactive基本內(nèi)容
注:本文示例代碼可在github查閱
https://github.com/jCodeLife/learn-vue3/tree/master/learn-vue3-reactive

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容