亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

解析vue3的ref,reactive的使用和原理

 更新時間:2022年09月28日 11:11:37   作者:KinHKin(五年前端)  
這篇文章主要介紹了vue3的ref,reactive的使用和原理解析,分析了?reactive?的實現(xiàn),以及?reactive?api?返回的?proxy?代理對象使用的?handlers?陷阱,并且對陷阱中我們最常用的?get?和?set?的源碼進行分析,需要的朋友可以參考下

1.前言

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

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ù)標注

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]

效果圖:

 在線地址:

KinHKin

https://rondsjinhuajin.github.io/DemoVue/#/

但是,這只是簡單的使用,配合了ts的類型標注,但是背后的原理是什么呢???

2.比較

先來做個ref,reactive的比較:

比較維度(kin注)refreactive
是否響應(yīng)式對象 JavaScript Proxy
創(chuàng)建的數(shù)據(jù)類型任何值類型的響應(yīng)式響應(yīng)式對象或數(shù)組
是否需要.value 屬性
復(fù)雜的類型標注Ref 這個類型interface自定義
隱式地從它的參數(shù)中推導(dǎo)類型
dom的更新異步異步
是否深層次響應(yīng)默認深層次默認深層次

不推薦使用 reactive() 的泛型參數(shù),因為處理了深層次 ref 解包的返回值與泛型參數(shù)的類型不同。

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

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

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

當(dāng) ref 在模板中作為頂層屬性被訪問時,它們會被自動“解包”,所以不需要使用 .value。下面是之前的計數(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>

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

3.ref源碼解析

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

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

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

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

 接下來就是走createRef這個方法:

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

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

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

如果rawValue本就是ref類型的會立即返回rawValue,否則返回一個RefImpl實例。

 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 是用來標識該對象是一個 ref 響應(yīng)式對象的標記與在講述 reactive api 時的 ReactiveFlag 相同。

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

當(dāng)我們通過 ref.value 的形式讀取該 ref 的值時,就會觸發(fā) value 的 getter 方法,在 getter 中會先通過 trackRefValue 收集該 ref 對象的 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)我們對 ref.value 進行修改時,又會觸發(fā) value 的 setter 方法,會將新舊 value 進行比較,如果值不同需要更新,則先更新新舊 value,之后通過 triggerRefValue 派發(fā)該 ref 對象的 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源碼解析

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

整體描述vue3的更新機制:

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

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

執(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ù) 確定對象是否為只讀對象,IS_READONLY key 確定對象是否為只讀對象。ReactiveFlags 枚舉會在源碼中不斷的與我們見面,所以有必要提前介紹一下 ReactiveFlags:

function isReadonly(value) {
return !!(value && value["__v_isReadonly" /* ReactiveFlags.IS_READONLY */]);

}

 

export const enum ReactiveFlags {
  SKIP = '__v_skip', // 是否跳過響應(yīng)式 返回原始對象
  IS_REACTIVE = '__v_isReactive', // 標記一個響應(yīng)式對象
  IS_READONLY = '__v_isReadonly', // 標記一個只讀對象
  RAW = '__v_raw' // 標記獲取原始值
  IS_SHALLOW  = '__v_isShallow' // 是否淺層次拷貝
}

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

createReactiveObject

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

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

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

  • target:目標對象,想要生成響應(yīng)式的原始對象。
  • isReadonly:生成的代理對象是否只讀。
  • baseHandlers:生成代理對象的 handler 參數(shù)。當(dāng) target 類型是 Array 或 Object 時使用該 handler。
  • collectionHandlers:當(dāng) target 類型是 Map、Set、WeakMap、WeakSet 時使用該 handler。
  • proxyMap:存儲生成代理對象后的 Map 對象。

這里需要注意的是 baseHandlers 和 collectionHandlers 的區(qū)別,這兩個參數(shù)會根據(jù) target 的類型進行判斷,最終選擇將哪個參數(shù)傳入 Proxy 的構(gòu)造函數(shù),當(dāng)做 handler 參數(shù)使用。

邏輯部分:

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
    // 如何不是對象 曝出警告 返回其原始值
    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
 
     // 如果目標已經(jīng)是一個代理,直接返回  KinHKin譯
    // 除非對一個響應(yīng)式對象執(zhí)行 readonly
 
    if (target["__v_raw" /* ReactiveFlags.RAW */] &&
        !(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) {
        return target;
    }
    // target already has corresponding Proxy
    // 目標已經(jīng)存在對應(yīng)的代理對象 KinHKin譯
    const existingProxy = proxyMap.get(target);
    if (existingProxy) {
        return existingProxy;
    }
    // only specific value types can be observed.
    // 只有白名單里的類型才能被創(chuàng)建響應(yīng)式對象  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ù)類型并不會被轉(zhuǎn)換成代理對象,而是直接返回原始值。

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

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

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

所以回顧 reactive api,我們可能會得到一個代理對象,也可能只是獲得傳入的 target 目標對象的原始值。

handles的組成

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

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

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

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

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

get陷阱

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

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

get 陷阱有 4 個類型,分別對應(yīng)不同的響應(yīng)式 API,從名稱中就可以知道對應(yīng)的 API 名稱,非常一目了然。而所有的 get 都是由 createGetter 函數(shù)生成的。所以接下來我們著重看一下 createGetter 的邏輯。

從函數(shù)的簽名看起,入?yún)⒂?個,一個是isReadonly,另一個是shallow,讓使用 get 陷阱的 api 按需使用。

function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) { }
}

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

createGetter 的邏輯:

function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        // 如果key是響應(yīng)式的對象  就返回不是只讀  *KinHKin注釋*
        if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
            return !isReadonly;
        }
        // 如果key是只讀對象  就返回只讀是true  *KinHKin注釋*
        else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
            return isReadonly;
        }
        // 如果key是淺層次響應(yīng)對象  就返回淺層次是true  *KinHKin注釋*
        else if (key === "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */) {
            return shallow;
        }
        // 如果key是原始值對象并且改變的值和原始標記一致  就返回原始值  *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 是一個對象,對象內(nèi)保存了若干個被特殊處理的數(shù)組方法,并以鍵值對的形式存儲。 *KinHKin注釋*
        if (!isReadonly && targetIsArray && shared.hasOwn(arrayInstrumentations, key)) {
            // 特殊處理數(shù)組返回結(jié)果 
            return Reflect.get(arrayInstrumentations, key, receiver);
        }
        // 獲取 Reflect 執(zhí)行的 get 默認結(jié)果
        const res = Reflect.get(target, key, receiver);
        // 如果是 key 是 Symbol,并且 key 是 Symbol 對象中的 Symbol 類型的 key
        // 或者 key 是不需要追蹤的 key: __proto__,__v_isRef,__isVue
        // 直接返回 get 結(jié)果 *KinHKin注釋*
        if (shared.isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
            return res;
        }
        // 不是只讀對象 執(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 類型時,不需要解包 *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)換成代理,我們在這里做 isObject 的檢查以避免無效值警告。
            // 也需要在這里惰性訪問只讀和星影視對象,以避免循環(huán)依賴。*KinHKin注釋*
            return isReadonly ? readonly(res) : reactive(res);
        }
        // 不是 object 類型則直接返回 get 結(jié)果 *KinHKin注釋*
        return res;
    };
}

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

get 的后續(xù)邏輯:

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

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

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

  • 對索引敏感的數(shù)組方法
  • includes、indexOf、lastIndexOf
  • 會改變自身長度的數(shù)組方法,需要避免 length 被依賴收集,因為這樣可能會造成循環(huán)引用
  • push、pop、shift、unshift、splice

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

  • __proto__
  • _v_isRef
  • __isVue

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

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

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

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

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

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

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

set陷阱

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

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

// 純函數(shù) 默認深層次響應(yīng) 函數(shù)不入?yún)?*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)對象,也不是只讀  更新舊值 新值為普通對象 *KinHKin*
            if (!isShallow(value) && !isReadonly(value)) {
                oldValue = toRaw(oldValue);
                value = toRaw(value);
            }
        // 當(dāng)不是 只讀 模式時,判斷舊值是否是 Ref,如果是則直接更新舊值的 value
        // 因為 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
        // 如果目標是原始對象原型鏈上的屬性,則不會觸發(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 的過程中會首先獲取新舊與舊值,當(dāng)目前的代理對象不是淺層比較時,會判斷舊值是否是一個 Ref,如果舊值不是數(shù)組且是一個 ref類型的對象,并且新值不是 ref 對象時,會直接修改舊值的 value。

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

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

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

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

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

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

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

5.總結(jié)

開始部分講解了ref,reactive的使用實例,如何進行類型的標注,配合ts這么使用,接著講解了兩者的區(qū)別,分別需要注意的點,還有ref的頂層自動解包。

后面講解了ref的底層實現(xiàn)的原理,源碼的分析,ref是一個類,它有自己的get,set方法去更新。不需要依賴底層的所以使用于所有的數(shù)據(jù)類型做響應(yīng)式。

為了讓大家屬性 Proxy 對響應(yīng)式系統(tǒng)的影響,著重介紹了響應(yīng)式基礎(chǔ) API:reactive。分析了 reactive 的實現(xiàn),以及 reactive api 返回的 proxy 代理對象使用的 handlers 陷阱。并且對陷阱中我們最常用的 get 和 set 的源碼進行分析,相信大家在看完本篇文章以后,對 proxy 這個 ES2015 的新特性的使用又有了新的理解。

本文只是介紹 Vue3 響應(yīng)式系統(tǒng)的第一篇文章,所以 track 收集依賴,trigger 派發(fā)更新的過程沒有詳細展開,在后續(xù)的文章中計劃詳細講解副作用函數(shù) effect,以及 track 和 trigger 的過程,如果希望能詳細了解響應(yīng)式系統(tǒng)的源碼,麻煩大家點個關(guān)注免得迷路。如果想繼續(xù)追蹤后續(xù)文章,也可以關(guān)注我的賬號或 follow 我的github。

到此這篇關(guān)于vue3的ref,reactive的使用和原理解析的文章就介紹到這了,更多相關(guān)vue3的ref reactive使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue 集成jTopo 處理方法

    vue 集成jTopo 處理方法

    這篇文章主要介紹了vue 集成jTopo 處理方法,非常不錯,具有一定的參考借鑒價值 ,需要的朋友可以參考下
    2019-08-08
  • Vue中引入使用patch-package為依賴打補丁問題

    Vue中引入使用patch-package為依賴打補丁問題

    這篇文章主要介紹了Vue中引入使用patch-package為依賴打補丁問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • 詳解使用vuex進行菜單管理

    詳解使用vuex進行菜單管理

    本篇文章主要介紹了詳解使用vuex進行菜單管理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • vue :src 文件路徑錯誤問題的解決方法

    vue :src 文件路徑錯誤問題的解決方法

    這篇文章主要介紹了vue :src 文件路徑錯誤問題的簡單解決方法,本文分步驟給大家介紹的非常詳細,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧
    2018-05-05
  • Vue項目中使用flow做類型檢測的方法

    Vue項目中使用flow做類型檢測的方法

    這篇文章主要介紹了Vue項目中使用flow做類型檢測的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Vue2.0 多 Tab切換組件的封裝實例

    Vue2.0 多 Tab切換組件的封裝實例

    本篇文章主要介紹了Vue2.0 多 Tab切換組件的封裝實例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • vue3實現(xiàn)按鈕權(quán)限管理的項目實踐

    vue3實現(xiàn)按鈕權(quán)限管理的項目實踐

    在做后臺管理系統(tǒng)時,經(jīng)常會有權(quán)限管理的功能,本文主要介紹了vue3實現(xiàn)按鈕權(quán)限管理的項目實踐,具有一定的參考價值,感興趣的可以了解一下
    2023-08-08
  • 一步一步實現(xiàn)Vue的響應(yīng)式(對象觀測)

    一步一步實現(xiàn)Vue的響應(yīng)式(對象觀測)

    這篇文章主要介紹了一步一步實現(xiàn)Vue的響應(yīng)式(對象觀測),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • Vue2.x通用編輯組件的封裝及應(yīng)用詳解

    Vue2.x通用編輯組件的封裝及應(yīng)用詳解

    這篇文章主要為大家詳細介紹了Vue2.x通用編輯組件的封裝及應(yīng)用,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • vue實現(xiàn)下拉框二級聯(lián)動效果的實例代碼

    vue實現(xiàn)下拉框二級聯(lián)動效果的實例代碼

    這篇文章主要介紹了vue實現(xiàn)下拉框二級聯(lián)動效果,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-11-11

最新評論