站長資訊網
        最全最豐富的資訊網站

        詳解vue3中reactive和ref的區別(源碼解析)

        vue中reactive和ref的區別是什么?下面本篇文章帶大家深入源碼徹底搞清vue3中reactive和ref的區別,希望對大家有所幫助!

        詳解vue3中reactive和ref的區別(源碼解析)

        如何快速入門VUE3.0:進入學習

        在vue3的日常開發中,我發現很多人都是基于自己的習慣reactiveref一把梭,雖然這樣都可以實現需求,既然這樣那為什么已經有了reactive還需要再去設計一個ref呢?這兩者的實際運用場景以及區別是什么呢?

        并且關于ref的底層邏輯,有的人說ref的底層邏輯還是reactive。有的人說ref的底層是classvalue只是這個class的一個屬性,那這兩種說法哪種正確呢?都有沒有依據呢?

        抱著這樣的疑問我們本次就深入源碼,徹底搞清vue3中reactiveref的區別。(學習視頻分享:vue視頻教程)

        不想看源碼的童鞋,可以直接拉到后面看總結

        reactive

        源碼地址:packages/reactivity/reactive.ts

        首先我們看一下vue3中用來標記目標對象target類型的ReactiveFlags

        // 標記目標對象 target 類型的 ReactiveFlags export const enum ReactiveFlags {   SKIP = '__v_skip',   IS_REACTIVE = '__v_isReactive',   IS_READONLY = '__v_isReadonly',   RAW = '__v_raw' }  export interface Target {   [ReactiveFlags.SKIP]?: boolean          // 不做響應式處理的數據   [ReactiveFlags.IS_REACTIVE]?: boolean   // target 是否是響應式   [ReactiveFlags.IS_READONLY]?: boolean   // target 是否是只讀   [ReactiveFlags.RAW]?: any               // 表示proxy 對應的源數據, target 已經是 proxy 對象時會有該屬性 }

        reactive

        export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> 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   }   // 創建 observe   return createReactiveObject(     target,     false,     mutableHandlers,     mutableCollectionHandlers,     reactiveMap   ) }

        reactive函數接收一個target對象,如果target對象只讀則直接返回該對象

        若非只讀則直接通過createReactiveObject創建observe對象

        createReactiveObject

        看著長不要怕,先貼createReactiveObject完整代碼,我們分段閱讀

        /**  *   * @param target 目標對象  * @param isReadonly 是否只讀  * @param baseHandlers 基本類型的 handlers  * @param collectionHandlers 主要針對(set、map、weakSet、weakMap)的 handlers  * @param proxyMap  WeakMap數據結構  * @returns   */  function createReactiveObject(   target: Target,   isReadonly: boolean,   baseHandlers: ProxyHandler<any>,   collectionHandlers: ProxyHandler<any>,   proxyMap: WeakMap<Target, any> ) {    // typeof 不是 object 類型的,在開發模式拋出警告,生產環境直接返回目標對象   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   // 已經是響應式的就直接返回(取ReactiveFlags.RAW 屬性會返回true,因為進行reactive的過程中會用weakMap進行保存,   // 通過target能判斷出是否有ReactiveFlags.RAW屬性)   // 例外:對reactive對象進行readonly()   if (     target[ReactiveFlags.RAW] &&     !(isReadonly && target[ReactiveFlags.IS_REACTIVE])   ) {     return target   }   // target already has corresponding Proxy   // 對已經Proxy的,則直接從WeakMap數據結構中取出這個Proxy對象   const existingProxy = proxyMap.get(target)   if (existingProxy) {     return existingProxy   }   // only a whitelist of value types can be observed.   // 只對targetTypeMap類型白名單中的類型進行響應式處理   const targetType = getTargetType(target)   if (targetType === TargetType.INVALID) {     return target   }   // proxy 代理 target   // (set、map、weakSet、weakMap) collectionHandlers   // (Object、Array) baseHandlers   const proxy = new Proxy(     target,     targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers   )   proxyMap.set(target, proxy)   return proxy }

        首先我們看到createReactiveObject接收了五個參數

          target: Target,   isReadonly: boolean,   baseHandlers: ProxyHandler<any>,   collectionHandlers: ProxyHandler<any>,   proxyMap: WeakMap<Target, any>

        target 目標對象

        isReadonly 是否只讀

        baseHandlers 基本類型的 handlers 處理數組,對象

        collectionHandlers 處理 set、map、weakSet、weakMap

        proxyMap WeakMap數據結構存儲副作用函數


        這里主要是通過ReactiveFlags.RAWReactiveFlags.IS_REACTIVE判斷是否是響應式數據,若是則直接返回該對象

         if (     target[ReactiveFlags.RAW] &&     !(isReadonly && target[ReactiveFlags.IS_REACTIVE])   ) {     return target   }

        對于已經是Proxy的,則直接從WeakMap數據結構中取出這個Proxy對象并返回

          const existingProxy = proxyMap.get(target)   if (existingProxy) {     return existingProxy   }

        這里則是校驗了一下當前target的類型是不是ObjectArrayMapSetWeakMapWeakSet,如果都不是則直接返回該對象,不做響應式處理

         // 只對targetTypeMap類型白名單中的類型進行響應式處理   const targetType = getTargetType(target)   if (targetType === TargetType.INVALID) {     return target   }

        校驗類型的邏輯

        function getTargetType(value: Target) {   return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)     ? TargetType.INVALID     : targetTypeMap(toRawType(value)) }  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   } }

        所有的前置校驗完后,就可以使用proxy 代理target對象了

        這里使用了一個三目運算符通過TargetType.COLLECTION來執行不同的處理邏輯

        • (set、map、weakSet、weakMap) 使用 collectionHandlers
        • (Object、Array) 使用 baseHandlers
        // proxy 代理 target   // (set、map、weakSet、weakMap) collectionHandlers   // (Object、Array) baseHandlers   const proxy = new Proxy(     target,     targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers   )   proxyMap.set(target, proxy)   return proxy

        現在對createReactiveObject的執行邏輯是不是就很清晰了

        到這里還沒有結束,createReactiveObject中最后proxy是如何去代理target的呢?這里我們用baseHandlers舉例,深入baseHandlers的內部去看看

        baseHandlers

        源碼地址:packages/reactivity/baseHandlers.ts

        reactive.ts中我們可以看到一共引入了四種 handler

        import {   mutableHandlers,   readonlyHandlers,   shallowReactiveHandlers,   shallowReadonlyHandlers } from './baseHandlers'
        • mutableHandlers 可變處理
        • readonlyHandlers 只讀處理
        • shallowReactiveHandlers 淺觀察處理(只觀察目標對象的第一層屬性)
        • shallowReadonlyHandlers 淺觀察 && 只讀

        我們以mutableHandlers為例

        // 可變處理 // const get = /*#__PURE__*/ createGetter() // const set = /*#__PURE__*/ createSetter() // get、has、ownKeys 會觸發依賴收集 track() // set、deleteProperty 會觸發更新 trigger() export const mutableHandlers: ProxyHandler<object> = {   get,                  // 用于攔截對象的讀取屬性操作   set,                  // 用于攔截對象的設置屬性操作   deleteProperty,       // 用于攔截對象的刪除屬性操作   has,                  // 檢查一個對象是否擁有某個屬性   ownKeys               // 針對 getOwnPropertyNames,  getOwnPropertySymbols, keys 的代理方法 }

        這里的getset分別對應著createGetter()createSetter()

        • createGetter()

        先上完整版代碼

        /**  * 用于攔截對象的讀取屬性操作  * @param isReadonly 是否只讀  * @param shallow 是否淺觀察  * @returns   */ function createGetter(isReadonly = false, shallow = false) {   /**    * @param target 目標對象    * @param key 需要獲取的值的鍵值    * @param receiver 如果遇到 setter,receiver 則為setter調用時的this值    */   return function get(target: Target, key: string | symbol, receiver: object) {     // ReactiveFlags 是在reactive中聲明的枚舉值,如果key是枚舉值則直接返回對應的布爾值     if (key === ReactiveFlags.IS_REACTIVE) {       return !isReadonly     } else if (key === ReactiveFlags.IS_READONLY) {       return isReadonly     } else if (       // 如果key是raw  receiver 指向調用者,則直接返回目標對象。       // 這里判斷是為了保證觸發攔截 handle 的是 proxy 本身而不是 proxy 的繼承者       // 觸發攔的兩種方式:一是訪問 proxy 對象本身的屬性,二是訪問對象原型鏈上有 proxy 對象的對象的屬性,因為查詢會沿著原型鏈向下找       key === ReactiveFlags.RAW &&       receiver ===         (isReadonly           ? shallow             ? shallowReadonlyMap             : readonlyMap           : shallow           ? shallowReactiveMap           : reactiveMap         ).get(target)     ) {       return target     }      const targetIsArray = isArray(target)     // 如果目標對象 不為只讀、是數組、key屬于arrayInstrumentations:['includes', 'indexOf', 'lastIndexOf']方法之一,即觸發了這三個方法之一     if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {       // 通過 proxy 調用,arrayInstrumentations[key]的this一定指向 proxy       return Reflect.get(arrayInstrumentations, key, receiver)     }      const res = Reflect.get(target, key, receiver)      // 如果 key 是 symbol 內置方法,或者訪問的是原型對象__proto__,直接返回結果,不收集依賴     if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {       return res     }      // 不是只讀類型的 target 就收集依賴。因為只讀類型不會變化,無法觸發 setter,也就會觸發更新     if (!isReadonly) {       track(target, TrackOpTypes.GET, key)     }      // 如果是淺觀察,不做遞歸轉化,就是說對象有屬性值還是對象的話不遞歸調用 reactive()     if (shallow) {       return res     }      // 如果get的結果是ref     if (isRef(res)) {       // ref unwrapping - does not apply for Array + integer key.       // 返回 ref.value,數組除外       const shouldUnwrap = !targetIsArray || !isIntegerKey(key)       return shouldUnwrap ? res.value : res     }      // 由于 proxy 只能代理一層,如果子元素是對象,需要遞歸繼續代理     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   } }

        看著長,最終就是track()依賴收集

        track()依賴收集內容過多,和trigger()觸發更新一起,單開一篇文章

        • createSetter()

        /**  * 攔截對象的設置屬性操作  * @param shallow 是否是淺觀察  * @returns   */ function createSetter(shallow = false) {   /**    * @param target 目標對象    * @param key 設置的屬性名稱    * @param value 要改變的屬性值    * @param receiver 如果遇到setter,receiver則為setter調用時的this值    */   return function set(     target: object,     key: string | symbol,     value: unknown,     receiver: object   ): boolean {     let oldValue = (target as any)[key]     // 如果模式不是淺觀察模式     if (!shallow) {       // 拿新值和老值的原始值,因為新傳入的值可能是響應式數據,如果直接和 target 上原始值比較是沒有意義的       value = toRaw(value)       oldValue = toRaw(oldValue)       // 目標對象不是數組,舊值是ref,新值不是ref,則直接賦值,這里提到ref       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     // reactive是proxy實例才觸發更新,防止通過原型鏈觸發攔截器觸發更新     if (target === toRaw(receiver)) {       if (!hadKey) {         // 如果不存在則trigger ADD         trigger(target, TriggerOpTypes.ADD, key, value)       } else if (hasChanged(value, oldValue)) {         // 如果新舊值不相等則trigger SET         trigger(target, TriggerOpTypes.SET, key, value, oldValue)       }     }     return result   } }

        trigger()觸發更新

        ref

        源碼地址:packages/reactivity/src/ref.ts

        接收一個可選unknown,接著直接調用createRef()

        export function ref(value?: unknown) {   return createRef(value, false) }

        詳解vue3中reactive和ref的區別(源碼解析)

        ref的區別就是在調用createRef()時第二個值傳的是true

        export function shallowRef(value?: unknown) {   return createRef(value, true) }

        看一下官方文檔上對shallowRef的解釋

        詳解vue3中reactive和ref的區別(源碼解析)

        createRef

        通過isRef()判斷是否是ref數據,是則直接返回該數據,不是則通過new RefImpl創建ref數據

        在創建時會傳兩個值一個是rawValue(原始值),一個是shallow(是否是淺觀察),具體使用場景可看上面refshallowRef的介紹

        function createRef(rawValue: unknown, shallow: boolean) {   // 是否是 ref 數據   if (isRef(rawValue)) {     return rawValue   }   return new RefImpl(rawValue, shallow) }
        • isRef()

        通過__v_isRef只讀屬性判斷是否是ref數據,此屬性會在RefImpl創建ref數據時添加

        export function isRef(r: any): r is Ref {   return Boolean(r && r.__v_isRef === true) }

        RefImpl

        class RefImpl<T> {   private _value: T   private _rawValue: T    public dep?: Dep = undefined   // 只讀屬性 __v_isRef 判斷是否是ref數據的靜態標識   public readonly __v_isRef = true    constructor(value: T, public readonly _shallow: boolean) {     this._rawValue = _shallow ? value : toRaw(value)  // 非淺觀察用toRaw()包裹原始值     this._value = _shallow ? value : toReactive(value) // 非淺觀察用toReactive()處理數據   }    get value() {   // 依賴收集     trackRefValue(this)     return this._value   }    set value(newVal) {     newVal = this._shallow ? newVal : toRaw(newVal) // 非淺觀察用toRaw()包裹值     // 兩個值不相等     if (hasChanged(newVal, this._rawValue)) {       this._rawValue = newVal       this._value = this._shallow ? newVal : toReactive(newVal)       triggerRefValue(this, newVal) // 觸發依賴,派發更新     }   } }

        根據RefImpl我們可以看到ref的底層邏輯,如果是對象確實會使用reactive進行處理,并且ref的創建使用的也是RefImpl class實例,value只是RefImpl的屬性

        在我們訪問設置 ref的value值時,也分別是通過getset攔截進行依賴收集派發更新

        • toReactive

        我們來看一下toReactive()這個方法,在RefImpl中創建ref數據時會調用toReactive()方法,這里會先判斷傳進來的值是不是對象,如果是就用reactive()包裹,否則就返回其本身

        export const toReactive = <T extends unknown>(value: T): T =>   isObject(value) ? reactive(value) : value
        • trackRefValue

        ref的依賴收集方法

        export function trackRefValue(ref: RefBase<any>) {   if (isTracking()) {     ref = toRaw(ref)     if (!ref.dep) {       ref.dep = createDep()     }     if (__DEV__) {       trackEffects(ref.dep, {         target: ref,         type: TrackOpTypes.GET,         key: 'value'       })     } else {       trackEffects(ref.dep)     }   } }
        • triggerRefValue

        ref的派發更新方法

        export function triggerRefValue(ref: RefBase<any>, newVal?: any) {   ref = toRaw(ref)   if (ref.dep) {     if (__DEV__) {       triggerEffects(ref.dep, {         target: ref,         type: TriggerOpTypes.SET,         key: 'value',         newValue: newVal       })     } else {       triggerEffects(ref.dep)     }   } }

        總結

        看完reactiveref源碼,相信對本文一開始的幾個問題也都有了答案,這里也總結了幾個問題:

        • 問:ref的底層邏輯是什么,具體是如何實現的

        答:ref底層會通過 new RefImpl()來創造ref數據,在new RefImpl()會首先給數據添加__v_isRef只讀屬性用來標識ref數據。而后判斷傳入的值是否是對象,如果是對象則使用toReactive()處理成reactive,并將值賦給RefImpl()value屬性上。在訪問設置ref數據的value時會分別觸發依賴收集派發更新流程。


        • 問:ref底層是否會使用reactive處理數據

        答:RefImpl中非淺觀察會調用toReactive()方法處理數據,toReactive()中會先判斷傳入的值是不是一個對象,如果是對象則使用reactive進行處理,不是則直接返回值本身。


        • 問:為什么已經有了reactive還需要在設計一個ref呢?

        答: 因為vue3響應式方案使用的是proxy,而proxy的代理目標必須是非原始值,沒有任何方式能去攔截對原始值的操作,所以就需要一層對象作為包裹,間接實現原始值的響應式方案。


        • 問:為什么ref數據必須要有個value屬性,訪問ref數據必須要通過.value的方式呢?

        答:這是因為要解決響應式丟失的問題,舉個例子:

        // obj是響應式數據 const obj = reactive({ foo: 1, bar: 2 })  // newObj 對象下具有與 obj對象同名的屬性,并且每個屬性值都是一個對象 // 該對象具有一個訪問器屬性 value,當讀取 value的值時,其實讀取的是 obj 對象下相應的屬性值  const newObj = {     foo: {         get value() {             return obj.foo         }     },     bar: {         get value() {             return obj.bar         }     } }  effect(() => {     // 在副作用函數內通過新對象 newObj 讀取 foo 的屬性值     console.log(newObj.foo) }) // 正常觸發響應 obj.foo = 100

        可以看到,在現在的newObj對象下,具有與obj對象同名的屬性,而且每個屬性的值都是一個對象,例如foo 屬性的值是:

        {     get value() {         return obj.foo     } }

        該對象有一個訪問器屬性value,當讀取value的值時,最終讀取的是響應式數據obj下的同名屬性值。也就是說,當在副作用函數內讀取newObj.foo時,等價于間接讀取了obj.foo的值。這樣響應式數據就能夠與副作用函數建立響應聯系

        (學習視頻分享:web前端開發、編程基礎視頻)

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 欧美精品一本久久男人的天堂| 久久国产综合精品五月天| 四虎精品8848ys一区二区| 亚洲精品一级无码鲁丝片 | 国产欧美久久久精品| 亚洲αv在线精品糸列| 精品国产香蕉伊思人在线在线亚洲一区二区| 99精品国产在热久久| 欧美肥屁VIDEOSSEX精品| 亚欧洲精品在线视频免费观看| 国产精品毛片一区二区| 日韩精品一区二区三区大桥未久| 国内精品久久久久伊人av| 亚洲国产精品无码久久久久久曰 | 国产亚洲曝欧美不卡精品| 久久精品一区二区国产| 国产精品涩涩涩视频网站 | 欧美精品成人3d在线| 国产综合免费精品久久久| 99久久精品免费看国产| 久久精品国产亚洲沈樵| 精品国精品国产| 国产高清国产精品国产专区| 经典国产乱子伦精品视频| 九九精品99久久久香蕉| 久久精品www人人爽人人| 日韩精品视频一区二区三区| 真实国产精品vr专区| 亚洲AV乱码久久精品蜜桃| 亚洲乱码国产乱码精品精| 亚洲色精品88色婷婷七月丁香| 日本Aⅴ大伊香蕉精品视频| 免费人成在线观看欧美精品 | 国产在线观看高清精品| 国产精品片在线观看手机版| 国产精品免费久久久久影院 | 精品一区二区三区中文字幕| 五月花精品视频在线观看| 国产精品成人久久久久久久| 国产精品成人免费观看| 精品精品国产国产|