溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

vue3中如何使用ref和reactive

發(fā)布時(shí)間:2023-05-20 14:53:23 來源:億速云 閱讀:129 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹“vue3中如何使用ref和reactive”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡(jiǎn)單快捷,實(shí)用性強(qiáng),希望這篇“vue3中如何使用ref和reactive”文章能幫助大家解決問題。

    1.前言

    vue3新增了ref,reactive兩個(gè)api用于響應(yīng)式數(shù)據(jù),Ref 系列毫無疑問是使用頻率最高的 api 之一,響應(yīng)式意味著數(shù)據(jù)變動(dòng),頁(yè)面局部自動(dòng)更新。數(shù)據(jù)類型有基本數(shù)據(jù)類型(string,number,boolean,undfined,null,symbol),引用數(shù)據(jù)類型(object,array,set,map等)。如何精準(zhǔn)檢測(cè)跟蹤js中所有的數(shù)據(jù)類型變動(dòng),并且能夠達(dá)到vnode的對(duì)比后真實(shí)dom的渲染?vue中是如何做到的呢?簡(jiǎn)單實(shí)例如下:

    import { reactive, ref } from "vue"; 
    import type { Ref } from "vue";
    // 定義響應(yīng)式數(shù)據(jù)
    const count: Ref<number> = ref(0);
    function countClick() {
            count.value++; // 更新數(shù)據(jù)
    }
    // 定義引用類型數(shù)據(jù)標(biāo)注
     
    interface TypeForm {
            name: string;
            num: number;
            list?: Array<[]>;
    }
    const formInline: TypeForm = reactive({
            name: "",
            num: 0,
    });
    formInline.name = 'KinHKin'
    formInline.num = 100
    formInline.list = [1,2,3,4]

    效果圖:

    vue3中如何使用ref和reactive

    2.比較

    先做個(gè)ref和reactive的比較

    vue3中如何使用ref和reactive

    不推薦使用 reactive() 的泛型參數(shù),因?yàn)樘幚砹松顚哟?ref 解包的返回值與泛型參數(shù)的類型不同。

    ref 被傳遞給函數(shù)或是從一般對(duì)象上被解構(gòu)時(shí),不會(huì)丟失響應(yīng)性:

    const obj = {
      foo: ref(1),
      bar: ref(2)
    }
     
    // 該函數(shù)接收一個(gè) ref
    // 需要通過 .value 取值
    // 但它會(huì)保持響應(yīng)性
    callSomeFunction(obj.foo)
     
    // 仍然是響應(yīng)式的
    const { foo, bar } = obj

    簡(jiǎn)而言之,ref() 讓我們能創(chuàng)造一種對(duì)任意值的 “引用”,并能夠在不丟失響應(yīng)性的前提下傳遞這些引用。這個(gè)功能很重要,因?yàn)樗?jīng)常用于將邏輯提取到 組合函數(shù) 中。

    當(dāng) ref 在模板中作為頂層屬性被訪問時(shí),它們會(huì)被自動(dòng)“解包”,所以不需要使用 .value。下面是之前的計(jì)數(shù)器例子,用 ref() 代替:

    <script setup>
    import { ref } from 'vue'
     
    const count = ref(0)
     
    function increment() {
      count.value++
    }
    </script>
     
    <template>
      <button @click="increment">
        {{ count }} <!-- 無需 .value -->
      </button>
    </template>

    請(qǐng)注意,僅當(dāng) ref 是模板渲染上下文的頂層屬性時(shí)才適用自動(dòng)“解包”。

    3.ref源碼解析

    對(duì)于vue3.2.2x版本的源碼位于node_moudles/@vue/reactivity/dist/reactivity.cjs.js文件中

    執(zhí)行順序是ref ->createRef ->new RefImpl 生成實(shí)例對(duì)象,提供get,set方法

    源碼中我們可以看到:入口有兩個(gè)函數(shù)默認(rèn)深層次響應(yīng)ref,淺層次使用shallowRef,參數(shù)一個(gè)false,一個(gè)是true。

    function ref(value) {
        return createRef(value, false);
    }
    function shallowRef(value) {
        return createRef(value, true);
    }

    接下來就是走createRef這個(gè)方法:

    function createRef(rawValue, shallow) {
        if (isRef(rawValue)) {
            return rawValue;
        }
        return new RefImpl(rawValue, shallow);
    }

    這個(gè)createRef方法接受兩個(gè)參數(shù),一個(gè)是傳入的基本類型的默認(rèn)數(shù)值,一個(gè)是否是深層次響應(yīng)的boolean值。

    function isRef(r) {
        return !!(r && r.__v_isRef === true);
    }

    如果rawValue本就是ref類型的會(huì)立即返回rawValue,否則返回一個(gè)RefImpl實(shí)例。

    RefImpl類:

    class RefImpl {
        constructor(value, __v_isShallow) {
            this.__v_isShallow = __v_isShallow;
            this.dep = undefined;
            this.__v_isRef = true;
            this._rawValue = __v_isShallow ? value : toRaw(value);
            this._value = __v_isShallow ? value : toReactive(value);
        }
        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 (shared.hasChanged(newVal, this._rawValue)) {
                this._rawValue = newVal;
                this._value = useDirectValue ? newVal : toReactive(newVal);
                triggerRefValue(this, newVal);
            }
        }
    }

    RefImpl類在構(gòu)造函數(shù)中,__v_isShallow表示是否是淺層次響應(yīng)的屬性, 私有的 _rawValue 變量,存放 ref 的舊值,_value是ref接受的最新的值。公共的只讀變量 __v_isRef 是用來標(biāo)識(shí)該對(duì)象是一個(gè) ref 響應(yīng)式對(duì)象的標(biāo)記與在講述 reactive api 時(shí)的 ReactiveFlag 相同。

    在const toReactive = (value) => shared.isObject(value) ? reactive(value) : value;這個(gè)函數(shù)的內(nèi)部判斷是否傳入的是一個(gè)對(duì)象,如果是一個(gè)對(duì)象就返回reactive返回代理對(duì)象,否則直接返回原參數(shù)。

    當(dāng)我們通過 ref.value 的形式讀取該 ref 的值時(shí),就會(huì)觸發(fā) value 的 getter 方法,在 getter 中會(huì)先通過 trackRefValue 收集該 ref 對(duì)象的 value 的依賴,收集完畢后返回該 ref 的值。

    function trackRefValue(ref) {
        if (shouldTrack && activeEffect) {
            ref = toRaw(ref);
            {
                trackEffects(ref.dep || (ref.dep = createDep()), {
                    target: ref,
                    type: "get" /* TrackOpTypes.GET */,
                    key: 'value'
                });
            }
        }
    }

    當(dāng)我們對(duì) ref.value 進(jìn)行修改時(shí),又會(huì)觸發(fā) value 的 setter 方法,會(huì)將新舊 value 進(jìn)行比較,如果值不同需要更新,則先更新新舊 value,之后通過 triggerRefValue 派發(fā)該 ref 對(duì)象的 value 屬性的更新,讓依賴該 ref 的副作用函數(shù)執(zhí)行更新。

    function triggerRefValue(ref, newVal) {
        ref = toRaw(ref);
        if (ref.dep) {
            {
                triggerEffects(ref.dep, {
                    target: ref,
                    type: "set" /* TriggerOpTypes.SET */,
                    key: 'value',
                    newValue: newVal
                });
            }
        }
    }

    4.reactive源碼解析

    對(duì)于vue3.2.2x版本的源碼位于node_moudles/@vue/reactivity/dist/reactivity.cjs.js文件中

    整體描述vue3的更新機(jī)制:

    在 Vue3 中,通過 track 的處理器函數(shù)來收集依賴,通過 trigger 的處理器函數(shù)來派發(fā)更新,每個(gè)依賴的使用都會(huì)被包裹到一個(gè)副作用(effect)函數(shù)中,而派發(fā)更新后就會(huì)執(zhí)行副作用函數(shù),這樣依賴處的值就被更新了。

    Proxy 對(duì)象能夠利用 handler 陷阱在 get、set 時(shí)捕獲到任何變動(dòng),也能監(jiān)聽對(duì)數(shù)組索引的改動(dòng)以及 數(shù)組 length 的改動(dòng)。

    執(zhí)行順序是:reactive -> createReactiveObject ->

    function reactive(target) {
        // if trying to observe a readonly proxy, return the readonly version.
        if (isReadonly(target)) {
            return target;
        }
        return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
    }

    第三行 isReadonly 函數(shù) 確定對(duì)象是否為只讀對(duì)象,IS_READONLY key 確定對(duì)象是否為只讀對(duì)象。ReactiveFlags 枚舉會(huì)在源碼中不斷的與我們見面,所以有必要提前介紹一下 ReactiveFlags:

    function isReadonly(value) {
     
    return !!(value && value["__v_isReadonly" /* ReactiveFlags.IS_READONLY */]);
     
    }
    export const enum ReactiveFlags {
      SKIP = '__v_skip', // 是否跳過響應(yīng)式 返回原始對(duì)象
      IS_REACTIVE = '__v_isReactive', // 標(biāo)記一個(gè)響應(yīng)式對(duì)象
      IS_READONLY = '__v_isReadonly', // 標(biāo)記一個(gè)只讀對(duì)象
      RAW = '__v_raw' // 標(biāo)記獲取原始值
      IS_SHALLOW  = '__v_isShallow' // 是否淺層次拷貝
    }

    在 ReactiveFlags 枚舉中有 5 個(gè)枚舉值,這五個(gè)枚舉值的含義都在注釋里。對(duì)于 ReactiveFlags 的使用是代理對(duì)象對(duì) handler 中的 trap 陷阱非常好的應(yīng)用,對(duì)象中并不存在這些 key,而通過 get 訪問這些 key 時(shí),返回值都是通過 get 陷阱的函數(shù)內(nèi)處理的。介紹完 ReactiveFlags 后我們繼續(xù)往下看。

    createReactiveObject

    入?yún)⒉糠郑?/strong>

    function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {}

    先看 createReactiveObject 函數(shù)的簽名,該函數(shù)接受 5 個(gè)參數(shù):

    1、target:目標(biāo)對(duì)象,想要生成響應(yīng)式的原始對(duì)象。
    2、isReadonly:生成的代理對(duì)象是否只讀。
    3、baseHandlers:生成代理對(duì)象的 handler 參數(shù)。當(dāng) target 類型是 Array 或 Object 時(shí)使用該 handler。
    4、collectionHandlers:當(dāng) target 類型是 Map、Set、WeakMap、WeakSet 時(shí)使用該 handler。
    5、proxyMap:存儲(chǔ)生成代理對(duì)象后的 Map 對(duì)象。
    這里需要注意的是 baseHandlers 和 collectionHandlers 的區(qū)別,這兩個(gè)參數(shù)會(huì)根據(jù) target 的類型進(jìn)行判斷,最終選擇將哪個(gè)參數(shù)傳入 Proxy 的構(gòu)造函數(shù),當(dāng)做 handler 參數(shù)使用。

    邏輯部分:

    function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
        // 如何不是對(duì)象 曝出警告 返回其原始值
        if (!shared.isObject(target)) {
            {
                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
     
         // 如果目標(biāo)已經(jīng)是一個(gè)代理,直接返回  KinHKin譯
        // 除非對(duì)一個(gè)響應(yīng)式對(duì)象執(zhí)行 readonly
     
        if (target["__v_raw" /* ReactiveFlags.RAW */] &&
            !(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) {
            return target;
        }
        // target already has corresponding Proxy
        // 目標(biāo)已經(jīng)存在對(duì)應(yīng)的代理對(duì)象 KinHKin譯
        const existingProxy = proxyMap.get(target);
        if (existingProxy) {
            return existingProxy;
        }
        // only specific value types can be observed.
        // 只有白名單里的類型才能被創(chuàng)建響應(yīng)式對(duì)象  KinHKin譯
        const targetType = getTargetType(target);
        if (targetType === 0 /* TargetType.INVALID */) {
            return target;
        }
        const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
        proxyMap.set(target, proxy);
        return proxy;
    }

    在該函數(shù)的邏輯部分,可以看到基礎(chǔ)數(shù)據(jù)類型并不會(huì)被轉(zhuǎn)換成代理對(duì)象,而是直接返回原始值。

    并且會(huì)將已經(jīng)生成的代理對(duì)象緩存進(jìn)傳入的 proxyMap,當(dāng)這個(gè)代理對(duì)象已存在時(shí)不會(huì)重復(fù)生成,會(huì)直接返回已有對(duì)象。

    也會(huì)通過 TargetType 來判斷 target 目標(biāo)對(duì)象的類型,Vue3 僅會(huì)對(duì) Array、Object、Map、Set、WeakMap、WeakSet 生成代理,其他對(duì)象會(huì)被標(biāo)記為 INVALID,并返回原始值。

    當(dāng)目標(biāo)對(duì)象通過類型校驗(yàn)后,會(huì)通過 new Proxy() 生成一個(gè)代理對(duì)象 proxy,handler 參數(shù)的傳入也是與 targetType 相關(guān),并最終返回已生成的 proxy 對(duì)象。

    所以回顧 reactive api,我們可能會(huì)得到一個(gè)代理對(duì)象,也可能只是獲得傳入的 target 目標(biāo)對(duì)象的原始值。

    handles的組成

    在 @vue/reactive 庫(kù)中有 baseHandlers 和 collectionHandlers 兩個(gè)模塊,分別生成 Proxy 代理的 handlers 中的 trap 陷阱。

    例如在上面生成 reactive 的 api 中 baseHandlers 的參數(shù)傳入了一個(gè) mutableHandlers 對(duì)象,這個(gè)對(duì)象是這樣的:

    const mutableHandlers = {
        get,
        set,
        deleteProperty,
        has,
        ownKeys
    };

    通過變量名我們能知道 mutableHandlers 中存在 5 個(gè) trap 陷阱。而在 baseHandlers 中,get 和 set 都是通過工廠函數(shù)生成的,以便于適配除 reactive 外的其他 api,例如 readonly、shallowReactive、shallowReadonly 等。

    baseHandlers 是處理 Array、Object 的數(shù)據(jù)類型的,這也是我們絕大部分時(shí)間使用 Vue3 時(shí)使用的類型,所以筆者接下來著重的講一下baseHandlers 中的 get 和 set 陷阱。

    get陷阱

    上一段提到 get 是由一個(gè)工廠函數(shù)生成的,先來看一下 get 陷阱的種類。

    const get = /*#__PURE__*/ createGetter();
    const shallowGet = /*#__PURE__*/ createGetter(false, true);
    const readonlyGet = /*#__PURE__*/ createGetter(true);
    const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true);

    函數(shù)內(nèi)部返回一個(gè)get函數(shù),使用了閉包的方式,將get函數(shù)中的參數(shù)傳到handlers中。

    createGetter 的邏輯:

    function createGetter(isReadonly = false, shallow = false) {
        return function get(target, key, receiver) {
            // 如果key是響應(yīng)式的對(duì)象  就返回不是只讀  *KinHKin注釋*
            if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
                return !isReadonly;
            }
            // 如果key是只讀對(duì)象  就返回只讀是true  *KinHKin注釋*
            else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
                return isReadonly;
            }
            // 如果key是淺層次響應(yīng)對(duì)象  就返回淺層次是true  *KinHKin注釋*
            else if (key === "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */) {
                return shallow;
            }
            // 如果key是原始值對(duì)象并且改變的值和原始標(biāo)記一致  就返回原始值  *KinHKin注釋*
            else if (key === "__v_raw" /* ReactiveFlags.RAW */ &&
                receiver ===
                    (isReadonly
                        ? shallow
                            ? shallowReadonlyMap
                            : readonlyMap
                        : shallow
                            ? shallowReactiveMap
                            : reactiveMap).get(target)) {
                return target;
            }
            // 判斷傳入的值是不是數(shù)組
            const targetIsArray = shared.isArray(target);
            // 如果不是只讀 并且是數(shù)組
            // arrayInstrumentations 是一個(gè)對(duì)象,對(duì)象內(nèi)保存了若干個(gè)被特殊處理的數(shù)組方法,并以鍵值對(duì)的形式存儲(chǔ)。 *KinHKin注釋*
            if (!isReadonly && targetIsArray && shared.hasOwn(arrayInstrumentations, key)) {
                // 特殊處理數(shù)組返回結(jié)果 
                return Reflect.get(arrayInstrumentations, key, receiver);
            }
            // 獲取 Reflect 執(zhí)行的 get 默認(rèn)結(jié)果
            const res = Reflect.get(target, key, receiver);
            // 如果是 key 是 Symbol,并且 key 是 Symbol 對(duì)象中的 Symbol 類型的 key
            // 或者 key 是不需要追蹤的 key: __proto__,__v_isRef,__isVue
            // 直接返回 get 結(jié)果 *KinHKin注釋*
            if (shared.isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
                return res;
            }
            // 不是只讀對(duì)象 執(zhí)行 track 收集依賴 *KinHKin注釋*
            if (!isReadonly) {
                track(target, "get" /* TrackOpTypes.GET */, key);
            }
            // 是淺層次響應(yīng) 直接返回 get 結(jié)果 *KinHKin注釋*
            if (shallow) {
                return res;
            }
            if (isRef(res)) {
                // ref unwrapping - skip unwrap for Array + integer key.
                // 如果是 ref ,則返回解包后的值 - 當(dāng) target 是數(shù)組,key 是 int 類型時(shí),不需要解包 *KinHKin注釋*
                return targetIsArray && shared.isIntegerKey(key) ? res : res.value;
            }
            if (shared.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.
                // 將返回的值也轉(zhuǎn)換成代理,我們?cè)谶@里做 isObject 的檢查以避免無效值警告。
                // 也需要在這里惰性訪問只讀和星影視對(duì)象,以避免循環(huán)依賴。*KinHKin注釋*
                return isReadonly ? readonly(res) : reactive(res);
            }
            // 不是 object 類型則直接返回 get 結(jié)果 *KinHKin注釋*
            return res;
        };
    }

    從這段 createGetter 邏輯中,之前專門介紹過的 ReactiveFlags 枚舉在這就取得了妙用。其實(shí)目標(biāo)對(duì)象中并沒有這些 key,但是在 get 中Vue3 就對(duì)這些 key 做了特殊處理,當(dāng)我們?cè)趯?duì)象上訪問這幾個(gè)特殊的枚舉值時(shí),就會(huì)返回特定意義的結(jié)果。而可以關(guān)注一下 ReactiveFlags.IS_REACTIVE 這個(gè) key 的判斷方式,為什么是只讀標(biāo)識(shí)的取反呢?因?yàn)楫?dāng)一個(gè)對(duì)象的訪問能觸發(fā)這個(gè) get 陷阱時(shí),說明這個(gè)對(duì)象必然已經(jīng)是一個(gè) Proxy 對(duì)象了,所以只要不是只讀的,那么就可以認(rèn)為是響應(yīng)式對(duì)象了。

    get 的后續(xù)邏輯:

    繼續(xù)判斷 target 是否是一個(gè)數(shù)組,如果代理對(duì)象不是只讀的,并且 target 是一個(gè)數(shù)組,并且訪問的 key 在數(shù)組需要特殊處理的方法里,就會(huì)直接調(diào)用特殊處理的數(shù)組函數(shù)執(zhí)行結(jié)果,并返回。

    arrayInstrumentations 是一個(gè)對(duì)象,對(duì)象內(nèi)保存了若干個(gè)被特殊處理的數(shù)組方法,并以鍵值對(duì)的形式存儲(chǔ)。

    我們之前說過 Vue2 以原型鏈的方式劫持了數(shù)組,而在這里也有類似地作用,下面是需要特殊處理的數(shù)組。

    • 對(duì)索引敏感的數(shù)組方法

      • includes、indexOf、lastIndexOf

    • 會(huì)改變自身長(zhǎng)度的數(shù)組方法,需要避免 length 被依賴收集,因?yàn)檫@樣可能會(huì)造成循環(huán)引用

      • push、pop、shift、unshift、splice

    下面的幾個(gè)key是不需要被依賴收集或者是返回響應(yīng)式結(jié)果的:

    __proto__
    _v_isRef
    __isVue

    在處理完數(shù)組后,我們對(duì) target 執(zhí)行 Reflect.get 方法,獲得默認(rèn)行為的 get 返回值。

    之后判斷 當(dāng)前 key 是否是 Symbol,或者是否是不需要追蹤的 key,如果是的話直接返回 get 的結(jié)果 res。

    接著判斷當(dāng)前代理對(duì)象是否是只讀對(duì)象,如果不是只讀的話,則運(yùn)行筆者上文提及的 tarck 處理器函數(shù)收集依賴。

    如果是 shallow 的淺層響應(yīng)式,則不需要將內(nèi)部的屬性轉(zhuǎn)換成代理,直接返回 res。

    如果 res 是一個(gè) Ref 類型的對(duì)象,就會(huì)自動(dòng)解包返回,這里就能解釋官方文檔中提及的 ref 在 reactive 中會(huì)自動(dòng)解包的特性了。而需要注意的是,當(dāng) target 是一個(gè)數(shù)組類型,并且 key 是 int 類型時(shí),即使用索引訪問數(shù)組元素時(shí),不會(huì)被自動(dòng)解包。

    如果 res 是一個(gè)對(duì)象,就會(huì)將該對(duì)象轉(zhuǎn)成響應(yīng)式的 Proxy 代理對(duì)象返回,再結(jié)合我們之前分析的緩存已生成的 proxy 對(duì)象,可以知道這里的邏輯并不會(huì)重復(fù)生成相同的 res,也可以理解文檔中提及的當(dāng)我們?cè)L問 reactive 對(duì)象中的 key 是一個(gè)對(duì)象時(shí),它也會(huì)自動(dòng)的轉(zhuǎn)換成響應(yīng)式對(duì)象,而且由于在此處生成 reactive 或者 readonly 對(duì)象是一個(gè)延遲行為,不需要在第一時(shí)間就遍歷 reactive 傳入的對(duì)象中的所有 key,也對(duì)性能的提升是一個(gè)幫助。

    當(dāng) res 都不滿足上述條件時(shí),直接返回 res 結(jié)果。例如基礎(chǔ)數(shù)據(jù)類型就會(huì)直接返回結(jié)果,而不做特殊處理。最后,get 陷阱的邏輯全部結(jié)束了。

    set陷阱

    set 也有一個(gè) createSetter 的工廠函數(shù),也是通過柯里化的方式返回一個(gè) set 函數(shù)。

    set 的函數(shù)比較簡(jiǎn)短,所以這次一次性把寫好注釋的代碼放上來,先看代碼再講邏輯。

    // 純函數(shù) 默認(rèn)深層次響應(yīng) 函數(shù)不入?yún)?nbsp;*KinHKin*
    const set = /*#__PURE__*/ createSetter();
    // 純函數(shù) 淺層次響應(yīng) 函數(shù)入?yún)⑹莟rue *KinHKin*
    const shallowSet = /*#__PURE__*/ createSetter(true);
    function createSetter(shallow = false) {
        return function set(target, key, value, receiver) {
            let oldValue = target[key];
            // 如果原始值是只讀and是ref類型and新的value屬性不是ref類型  直接返回
            if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
                return false;
            }
            if (!shallow) {
            // 如果新的值不是淺層次響應(yīng)對(duì)象,也不是只讀  更新舊值 新值為普通對(duì)象 *KinHKin*
                if (!isShallow(value) && !isReadonly(value)) {
                    oldValue = toRaw(oldValue);
                    value = toRaw(value);
                }
            // 當(dāng)不是 只讀 模式時(shí),判斷舊值是否是 Ref,如果是則直接更新舊值的 value
            // 因?yàn)?nbsp;ref 有自己的 setter *KinHKin*
                if (!shared.isArray(target) && isRef(oldValue) && !isRef(value)) {
                    oldValue.value = value;
                    return true;
                }
            }
            // 判斷 target 中是否存在 key *KinHKin*
            const hadKey = shared.isArray(target) && shared.isIntegerKey(key)
                ? Number(key) < target.length
                : shared.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
            // 如果目標(biāo)是原始對(duì)象原型鏈上的屬性,則不會(huì)觸發(fā) trigger 派發(fā)更新  *KinHKin*
            if (target === toRaw(receiver)) {
                // 使用 trigger 派發(fā)更新,根據(jù) hadKey 區(qū)別調(diào)用事件
                if (!hadKey) {
                    trigger(target, "add" /* TriggerOpTypes.ADD */, key, value);
                }
                else if (shared.hasChanged(value, oldValue)) {
                    trigger(target, "set" /* TriggerOpTypes.SET */, key, value, oldValue);
                }
            }
            return result;
        };
    }

    在 set 的過程中會(huì)首先獲取新舊與舊值,當(dāng)目前的代理對(duì)象不是淺層比較時(shí),會(huì)判斷舊值是否是一個(gè) Ref,如果舊值不是數(shù)組且是一個(gè) ref類型的對(duì)象,并且新值不是 ref 對(duì)象時(shí),會(huì)直接修改舊值的 value。

    看到這里可能會(huì)有疑問,為什么要更新舊值的 value?如果你使用過 ref 這個(gè) api 就會(huì)知道,每個(gè) ref 對(duì)象的值都是放在 value 里的,而 ref 與 reactive 的實(shí)現(xiàn)是有區(qū)別的,ref 其實(shí)是一個(gè) class 實(shí)例,它的 value 有自己的 set ,所以就不會(huì)在這里繼續(xù)進(jìn)行 set 了。

    在處理完 ref 類型的值后,會(huì)聲明一個(gè)變量 hadKey,判斷當(dāng)前要 set 的 key 是否是對(duì)象中已有的屬性。

    接下來調(diào)用 Reflect.set 獲取默認(rèn)行為的 set 返回值 result。

    然后會(huì)開始派發(fā)更新的過程,在派發(fā)更新前,需要保證 target 和原始的 receiver 相等,target 不能是一個(gè)原型鏈上的屬性。

    之后開始使用 trigger 處理器函數(shù)派發(fā)更新,如果 hadKey 不存在,則是一個(gè)新增屬性,通過 TriggerOpTypes.ADD 枚舉來標(biāo)記。這里可以看到開篇分析 Proxy 強(qiáng)于 Object.defineProperty 的地方,會(huì)監(jiān)測(cè)到任何一個(gè)新增的 key,讓響應(yīng)式系統(tǒng)更強(qiáng)大。

    如果 key 是當(dāng)前 target 上已經(jīng)存在的屬性,則比較一下新舊值,如果新舊值不一樣,則代表屬性被更新,通過 TriggerOpTypes.SET 來標(biāo)記派發(fā)更新。

    在更新派發(fā)完后,返回 set 的結(jié)果 result,至此 set 結(jié)束。

    關(guān)于“vue3中如何使用ref和reactive”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI