欧美亚洲中文,在线国自产视频,欧洲一区在线观看视频,亚洲综合中文字幕在线观看

      1. <dfn id="rfwes"></dfn>
          <object id="rfwes"></object>
        1. 站長資訊網(wǎng)
          最全最豐富的資訊網(wǎng)站

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

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

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

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

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

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

          抱著這樣的疑問我們本次就深入源碼,徹底搞清vue3中reactiveref的區(qū)別。(學習視頻分享: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          // 不做響應式處理的數(shù)據(jù)   [ReactiveFlags.IS_REACTIVE]?: boolean   // target 是否是響應式   [ReactiveFlags.IS_READONLY]?: boolean   // target 是否是只讀   [ReactiveFlags.RAW]?: any               // 表示proxy 對應的源數(shù)據(jù), target 已經(jīng)是 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.   // 如果目標對象是一個只讀的響應數(shù)據(jù),則直接返回目標對象   if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {     return target   }   // 創(chuàng)建 observe   return createReactiveObject(     target,     false,     mutableHandlers,     mutableCollectionHandlers,     reactiveMap   ) }

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

          若非只讀則直接通過createReactiveObject創(chuàng)建observe對象

          createReactiveObject

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

          /**  *   * @param target 目標對象  * @param isReadonly 是否只讀  * @param baseHandlers 基本類型的 handlers  * @param collectionHandlers 主要針對(set、map、weakSet、weakMap)的 handlers  * @param proxyMap  WeakMap數(shù)據(jù)結構  * @returns   */  function createReactiveObject(   target: Target,   isReadonly: boolean,   baseHandlers: ProxyHandler<any>,   collectionHandlers: ProxyHandler<any>,   proxyMap: WeakMap<Target, any> ) {    // typeof 不是 object 類型的,在開發(fā)模式拋出警告,生產(chǎn)環(huán)境直接返回目標對象   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   // 已經(jīng)是響應式的就直接返回(取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   // 對已經(jīng)Proxy的,則直接從WeakMap數(shù)據(jù)結構中取出這個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接收了五個參數(shù)

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

          target 目標對象

          isReadonly 是否只讀

          baseHandlers 基本類型的 handlers 處理數(shù)組,對象

          collectionHandlers 處理 set、map、weakSet、weakMap

          proxyMap WeakMap數(shù)據(jù)結構存儲副作用函數(shù)


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

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

          對于已經(jīng)是Proxy的,則直接從WeakMap數(shù)據(jù)結構中取出這個Proxy對象并返回

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

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

           // 只對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來執(zhí)行不同的處理邏輯

          • (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

          現(xiàn)在對createReactiveObject的執(zhí)行邏輯是不是就很清晰了

          到這里還沒有結束,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 會觸發(fā)依賴收集 track() // set、deleteProperty 會觸發(fā)更新 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 指向調用者,則直接返回目標對象。       // 這里判斷是為了保證觸發(fā)攔截 handle 的是 proxy 本身而不是 proxy 的繼承者       // 觸發(fā)攔的兩種方式:一是訪問 proxy 對象本身的屬性,二是訪問對象原型鏈上有 proxy 對象的對象的屬性,因為查詢會沿著原型鏈向下找       key === ReactiveFlags.RAW &&       receiver ===         (isReadonly           ? shallow             ? shallowReadonlyMap             : readonlyMap           : shallow           ? shallowReactiveMap           : reactiveMap         ).get(target)     ) {       return target     }      const targetIsArray = isArray(target)     // 如果目標對象 不為只讀、是數(shù)組、key屬于arrayInstrumentations:['includes', 'indexOf', 'lastIndexOf']方法之一,即觸發(fā)了這三個方法之一     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 就收集依賴。因為只讀類型不會變化,無法觸發(fā) setter,也就會觸發(fā)更新     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,數(shù)組除外       const shouldUnwrap = !targetIsArray || !isIntegerKey(key)       return shouldUnwrap ? res.value : res     }      // 由于 proxy 只能代理一層,如果子元素是對象,需要遞歸繼續(xù)代理     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()觸發(fā)更新一起,單開一篇文章

          • 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) {       // 拿新值和老值的原始值,因為新傳入的值可能是響應式數(shù)據(jù),如果直接和 target 上原始值比較是沒有意義的       value = toRaw(value)       oldValue = toRaw(oldValue)       // 目標對象不是數(shù)組,舊值是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實例才觸發(fā)更新,防止通過原型鏈觸發(fā)攔截器觸發(fā)更新     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()觸發(fā)更新

          ref

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

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

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

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

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

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

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

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

          createRef

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

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

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

          通過__v_isRef只讀屬性判斷是否是ref數(shù)據(jù),此屬性會在RefImpl創(chuàng)建ref數(shù)據(jù)時添加

          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數(shù)據(jù)的靜態(tài)標識   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()處理數(shù)據(jù)   }    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) // 觸發(fā)依賴,派發(fā)更新     }   } }

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

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

          • toReactive

          我們來看一下toReactive()這個方法,在RefImpl中創(chuàng)建ref數(shù)據(jù)時會調用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的派發(fā)更新方法

          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的底層邏輯是什么,具體是如何實現(xiàn)的

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


          • 問:ref底層是否會使用reactive處理數(shù)據(jù)

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


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

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


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

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

          // obj是響應式數(shù)據(jù) 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(() => {     // 在副作用函數(shù)內通過新對象 newObj 讀取 foo 的屬性值     console.log(newObj.foo) }) // 正常觸發(fā)響應 obj.foo = 100

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

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

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

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

          贊(0)
          分享到: 更多 (0)
          網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號