<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      [vue3] Vue3源碼閱讀筆記 reactivity - collectionHandlers

      源碼位置:https://github.com/vuejs/core/blob/main/packages/reactivity/src/collectionHandlers.ts

      這個文件主要用于處理SetMapWeakSetWeakMap類型的攔截。

      攔截是為了什么?為什么要處理這些方法?

      Vue3實現響應式的思路是使用Proxy API在getter中收集依賴,在setter觸發更新。

      SetMap等這些內置集合類型比較特殊,舉個例子,我們在使用Map的實例對象的時候,我們一般不會在實例對象上面去添加屬性或者修改自定義屬性的值,而是通過其原型上的get/set方法來操作鍵值對。

      值得注意的是,我們僅通過調用原型上的方法來操作鍵值對,而不會去修改實例對象上的屬性。因此,我們僅需要給Proxy配置getter,不需要配置setter

      const map = new Map<any, any>();
      // √
      map.set('k1', 'v1');
      map.get('k1');
      
      // ×
      map.k1 = 'v1';
      map.k1;
      

      而Vue3實現響應式的需求是希望調用get/set方法也能正確地收集依賴、觸發更新。因此,需要對這些方法進行改造。

      從響應式原理的角度出發,我們需要思考對集合的讀和寫操作:

      • 在讀的時候收集依賴:與讀操作相關的方法,內部要執行track收集依賴;
        • 與讀操作相關的方法:gethassize(這個是屬性,也要處理)、forEach,以及返回迭代器對象的其它方法;
      • 在寫的時候觸發更新:與寫操作相關的方法,內部要執行trigger函數觸發更新;
        • 與寫操作相關的方法:addsetdeleteclear

      返回迭代器對象的方法有:

      const iteratorMethods = [
       'keys',
       'values',
       'entries',
       Symbol.iterator,
      ] as const
      

      其中Symbol.iterator是為了實現for of遍歷必須實現的接口,在JavaScript中的所有可迭代對象都要實現這個接口。

      正式開始閱讀代碼

      這個文件的代碼結構和baseHandlers不太一樣,這個文件是先分別實現對getsethassize等操作的攔截,然后再整合成一個getter返回。

      根據是否是shallowreadonly分別導出了四種handler

      • mutableCollectionHandlers
      • shallowCollectionHandlers
      • readonlyCollectionHandlers
      • shallowReadonlyCollectionHandlers

      export

      export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
        get: /*#__PURE__*/ createInstrumentationGetter(false, false),
      }
      
      export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
        get: /*#__PURE__*/ createInstrumentationGetter(false, true),
      }
      
      export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
        get: /*#__PURE__*/ createInstrumentationGetter(true, false),
      }
      
      export const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> =
        {
          get: /*#__PURE__*/ createInstrumentationGetter(true, true),
        }
      

      可以看到這些Handlers都是通過createInstrumentationGetter來返回getter,接下來看看createInstrumentationGetter內部是如何實現的。

      createInstrumentationGetter

      源碼:(分段解析在下面)

      function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
        // 根據是否是只讀和是否是淺層響應式來選擇不同的處理函數集
        const instrumentations = shallow
          ? isReadonly
            ? shallowReadonlyInstrumentations // 淺層只讀的處理函數集
            : shallowInstrumentations // 淺層的處理函數集
          : isReadonly
            ? readonlyInstrumentations // 只讀的處理函數集
            : mutableInstrumentations // 可變(非只讀)的處理函數集
      
        // 返回一個自定義的getter函數,用于處理特定的鍵
        return (
          target: CollectionTypes, // 目標集合類型
          key: string | symbol, // 被訪問的鍵
          receiver: CollectionTypes, // 代理或包裝過的集合
        ) => {
          // 檢查特殊標志鍵
          if (key === ReactiveFlags.IS_REACTIVE) {
            // 如果不是只讀的,返回true表示是響應式的
            return !isReadonly
          } else if (key === ReactiveFlags.IS_READONLY) {
            // 如果是只讀的,返回true表示是只讀的
            return isReadonly
          } else if (key === ReactiveFlags.RAW) {
            // 返回原始的目標集合
            return target
          }
      
          // 使用Reflect.get來獲取值
          // 如果instrumentations有這個鍵,并且這個鍵在目標集合中,則從instrumentations獲取
          // 否則直接從目標集合獲取
          return Reflect.get(
            hasOwn(instrumentations, key) && key in target
              ? instrumentations
              : target,
            key,
            receiver,
          )
        }
      }
      

      分段解讀代碼

      // 根據是否是只讀和是否是淺層響應式來選擇不同的處理函數集
        const instrumentations = shallow
          ? isReadonly
            ? shallowReadonlyInstrumentations // 淺層只讀的處理函數集
            : shallowInstrumentations // 淺層的處理函數集
          : isReadonly
            ? readonlyInstrumentations // 只讀的處理函數集
            : mutableInstrumentations // 可變(非只讀)的處理函數集
      

      這個函數根據isReadonlyisShallow選擇了不同的函數集,函數集里的函數是特殊處理過的,目的是為了使這些實例方法可以適應Vue的響應式系統。

      // 檢查特殊標志鍵
          if (key === ReactiveFlags.IS_REACTIVE) {
            // 如果不是只讀的,返回true表示是響應式的
            return !isReadonly
          } else if (key === ReactiveFlags.IS_READONLY) {
            // 如果是只讀的,返回true表示是只讀的
            return isReadonly
          } else if (key === ReactiveFlags.RAW) {
            // 返回原始的目標集合
            return target
          }
      

      對于Vue內部特有的key,比如ReactiveFlags,返回特定的內容。這些ReactiveFlags并不存在于對象上,只是在getter做攔截并返回。

      // 使用Reflect.get來獲取值
          // 如果instrumentations有這個鍵,并且這個鍵在目標集合中,則從instrumentations獲取
          // 否則直接從目標集合獲取
          return Reflect.get(
            hasOwn(instrumentations, key) && key in target
              ? instrumentations
              : target,
            key,
            receiver,
          )
      

      最后,使用Reflect.get方法執行對key的訪問并返回。這個時候會通過hasOwn(instrumentations, key)檢查訪問key是否在生成的函數集里:

      • 如果存在,那么應該應用特殊處理過的函數集里的函數;
      • 如果不存在,那么就用target身上原始的方法。

      createInstrumentations

      四種不同的函數集由createInstrumentations函數創建并返回。

      const [
        mutableInstrumentations,
        readonlyInstrumentations,
        shallowInstrumentations,
        shallowReadonlyInstrumentations,
      ] = /* #__PURE__*/ createInstrumentations()
      

      接下來是craeteInstrumentations的實現,是一段很長的代碼:

      這里只展示了mutableInstrucmentations的函數集,其它三個大同小異,其中的各種零碎的getsizehas...方法的處理在后文介紹。

      function createInstrumentations() {
        // 定義可變(非只讀)的響應式處理函數集
        const mutableInstrumentations: Instrumentations = {
          get(this: MapTypes, key: unknown) {
            // 獲取Map中的值,默認不是只讀且不是淺層
            return get(this, key)
          },
          get size() {
            // 獲取集合的大小
            return size(this as unknown as IterableCollections)
          },
          has, // 檢查集合中是否存在特定的值
          add, // 向集合中添加元素
          set, // 設置Map中的鍵值對
          delete: deleteEntry, // 從集合中刪除元素
          clear, // 清空集合
          forEach: createForEach(false, false), // 遍歷集合的元素
        }
      
        // 定義淺層的響應式處理函數集
        const shallowInstrumentations: Instrumentations = {
          ...
        }
      
        // 定義只讀的響應式處理函數集
        const readonlyInstrumentations: Instrumentations = {
          ...
        }
      
        // 定義淺層只讀的響應式處理函數集
        const shallowReadonlyInstrumentations: Instrumentations = {
          ...
        }
      
        // 定義迭代器方法列表
        const iteratorMethods = [
          'keys',
          'values',
          'entries',
          Symbol.iterator,
        ] as const
      
        // 為每個迭代器方法添加對應的響應式處理函數
        iteratorMethods.forEach(method => {
          mutableInstrumentations[method] = createIterableMethod(method, false, false)
          readonlyInstrumentations[method] = createIterableMethod(method, true, false)
          shallowInstrumentations[method] = createIterableMethod(method, false, true)
          shallowReadonlyInstrumentations[method] = createIterableMethod(
            method,
            true,
            true,
          )
        })
      
        // 返回包含所有處理函數集的數組
        return [
          mutableInstrumentations, // 可變(非只讀)的處理函數集
          readonlyInstrumentations, // 只讀的處理函數集
          shallowInstrumentations, // 淺層的處理函數集
          shallowReadonlyInstrumentations, // 淺層只讀的處理函數集
        ]
      }
      

      注意到迭代器方法也都做了特殊處理,這是因為迭代器方法返回迭代器對象,而不是操作對象本身,無法被Proxy攔截,故無法追蹤依賴。

      這里使用了createIterableMethod創建能夠適配響應式的版本。

      createIterableMethod

      返回迭代器對象的幾個需要處理的方法分別是:

      • keys
      • values
      • entries
      • Symbol.iterator

      前三個是string類型傳入,最后一個是symbol類型傳入。

      源碼

      function createIterableMethod(
        method: string | symbol, // 迭代器方法名,可以是字符串或符號
        isReadonly: boolean, // 是否為只讀的迭代器
        isShallow: boolean, // 是否為淺層迭代器
      ) {
        // 返回一個自定義的迭代器方法
        return function (
          this: IterableCollections, // 當前的集合對象
          ...args: unknown[] // 方法調用的參數
        ): Iterable<unknown> & Iterator<unknown> {
          // 獲取原始的集合對象
          const target = (this as any)[ReactiveFlags.RAW]
          const rawTarget = toRaw(target)
          const targetIsMap = isMap(rawTarget) // 判斷目標是否為Map類型
          const isPair =
            method === 'entries' || (method === Symbol.iterator && targetIsMap) // 判斷是否為鍵值對迭代
          const isKeyOnly = method === 'keys' && targetIsMap // 判斷是否為僅鍵迭代
          const innerIterator = target[method](...args) // 調用目標集合對象的迭代器方法
          const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive // 獲取包裝函數
      
          // 如果不是只讀的,追蹤迭代操作
          !isReadonly &&
            track(
              rawTarget, // 追蹤原始集合對象
              TrackOpTypes.ITERATE, // 追蹤操作類型為迭代
              isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY, // 特殊標記,用于區分鍵迭代和普通迭代
            )
      
          // 返回一個包裝過的迭代器,它返回包裝過的值
          return {
            // 實現迭代器
            next() {
              const { value, done } = innerIterator.next() // 調用內部迭代器的next方法
              return done
                ? { value, done } // 如果迭代完成,返回當前值和完成標志
                : {
                    value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), // 如果迭代未完成,返回包裝過的值
                    done, // 完成標志保持不變
                  }
            },
            // 實現可迭代協議
            [Symbol.iterator]() {
              return this
            },
          }
        }
      }
      
      

      Vue3在處理MapSet的時候并沒有分開處理,而是一起處理了,因為它們有許多名字相同的方法,分開處理可能會導致代碼更亂。

      對于entries的輸出,也就是[key, value]格式的遍歷,通過簡單的判斷處理了:

      const isPair =
            method === 'entries' || (method === Symbol.iterator && targetIsMap)
      

      方法處理

      這里僅記錄getset方法。

      get

      get方法是MapWeakMap獨有的,所以target類型是MapTypes

      查詢的targetkey都可能是響應式對象,都需要做toRaw獲取原始值。如果直接在響應式對象上做操作,則可能被Proxy捕獲到,從而記錄了不必要的依賴。

      返回值的時候需要根據target的類型進行對應的包裝,即toReactivetoShallowtoReadonly

      這是因為使用set的時候存的是rawValue,而返回的時候需要配合target的類型。

      源碼

      function get(
        target: MapTypes,  // 目標對象,類型是 MapTypes
        key: unknown,  // 要獲取值的鍵,類型是 unknown
        isReadonly = false,  // 是否只讀,默認值為 false
        isShallow = false  // 是否淺層響應,默認值為 false
      ) {
        // 確保如果 target 是響應式對象,操作的是它的原始對象
        target = (target as any)[ReactiveFlags.RAW]
        // 獲取 target 的原始對象
        const rawTarget = toRaw(target)
        // 獲取 key 的原始值
        const rawKey = toRaw(key)
        
        if (!isReadonly) {
          // 如果 key 與 rawKey 不同(即 key 是響應式對象),跟蹤對 key 的訪問
          if (hasChanged(key, rawKey)) {
            track(rawTarget, TrackOpTypes.GET, key)
          }
          // 跟蹤對 rawKey 的訪問
          track(rawTarget, TrackOpTypes.GET, rawKey)
        }
      
        // 獲取 target 原型上的 has 方法
        const { has } = getProto(rawTarget)
        // 根據 isShallow 和 isReadonly 選擇對應的包裝函數
        const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
        
        // 如果原始對象上存在 key,則返回包裝后的值
        if (has.call(rawTarget, key)) {
          return wrap(target.get(key))
        // 如果原始對象上存在 rawKey,則返回包裝后的值
        } else if (has.call(rawTarget, rawKey)) {
          return wrap(target.get(rawKey))
        // 如果 target 不是原始對象,則調用 target.get(key) 進行跟蹤
        } else if (target !== rawTarget) {
          // 確保在只讀的響應式 Map 中,嵌套的響應式 Map 也能進行依賴跟蹤
          target.get(key)
        }
      }
      
      

      最后為什么還要加一個判斷target!==rawTarget?

      這個判斷和一個bug有關:readonly() breaks reactivity of Map · Issue #3602 · vuejs/core (github.com)

      對應的fixfix(reactivity): fix the tracking when readonly + reactive is used for Map by HcySunYang · Pull Request #3604 · vuejs/core (github.com)

      背景

      在 Vue3 的響應式系統中,readonlyreactive 組合使用時可能會出現一些問題,特別是在處理嵌套結構時。例如,當你有一個 readonly 包裝的 reactive Map,并試圖在這個 Map 中獲取一個值,如果不進行額外處理,可能會導致嵌套的響應式 Map 無法正確進行依賴跟蹤。

      示例代碼

      const reactiveMap = reactive(new Map([['key', new Map([['nestedKey', 'value']])]]));
      const readonlyMap = readonly(reactiveMap);
      
      // 獲取嵌套的 Map
      const nestedMap = readonlyMap.get('key');
      
      // 嘗試獲取嵌套 Map 的值
      const value = nestedMap.get('nestedKey');
      
      

      在這種情況下,如果不進行額外處理,nestedMap 可能無法正確進行依賴跟蹤。因為直接操作 readonly 包裝的對象不會觸發響應式系統的依賴跟蹤。這意味著當 nestedKey 的值發生變化時,可能不會觸發相關的響應式更新。

      解決方法

      判斷target是否是響應式對象,如果是的話,手動調用get觸發對依賴的收集。

      注意到rawTarget是由toRaw(target)得到的,接下來看一下toRaw函數的實現:

      toRaw的源碼位置:core/packages/reactivity/src/reactive.ts at main · vuejs/core (github.com)

      export function toRaw<T>(observed: T): T {
      // 嘗試獲取raw對象
      const raw = observed && (observed as Target)[ReactiveFlags.RAW]
      // 如果存在raw對象,則遞歸調用;如果不存在,則表示當前的observed已經是原始對象
      return raw ? toRaw(raw) : observed
      }
      

      可以看到如果傳入的對象如果有ReactiveFlags.RAW這個key,就認為它是被Vue包裝過的對象,因為只有被reactivereadonly等API包裝過的對象會被Vue添加上ReactiveFlags.RAW屬性,記錄著原始對象的引用。

      這里需要遞歸調用是因為對象可能被多層包裝,比如readonly(reactive({}))

      回到Map的get方法的最后處理

      if (target !== rawTarget) {
      // 確保在只讀的響應式 Map 中,嵌套的響應式 Map 也能進行依賴跟蹤
      target.get(key)
      }
      
      • 如果target === rawTarget,則target是原始對象;

      • 如果target!==rawTarget,則target是包裝過的對象,可能是reactive包裝過的響應式對象,也可能是readonly包裝過的只讀對象;

        這里或許可以再優化?如果是只讀對象,就不追蹤依賴了。

      set

      Mapkey可能是原始值也可能是響應式對象,這里需要做類型判斷,并且對原始key和響應式key都做判斷。

      在開發環境下如果存在同一個原始對象的兩種類型的key,會輸出警告。

      因為這種不規范的寫法會保存兩份鍵值對,內容可能不一致。

      源碼

      function set(this: MapTypes, key: unknown, value: unknown, _isShallow = false) {
        // 如果值不是淺層的且不是只讀的,則獲取其原始值
        if (!_isShallow && !isShallow(value) && !isReadonly(value)) {
          value = toRaw(value)
        }
      
        // 獲取目標對象的原始對象
        const target = toRaw(this)
        const { has, get } = getProto(target)
      
        // 檢查目標對象是否已經存在該鍵
        let hadKey = has.call(target, key)
        if (!hadKey) {
          // 如果不存在,嘗試使用原始鍵進行再次檢查
          key = toRaw(key)
          hadKey = has.call(target, key)
        } else if (__DEV__) {
          // 在開發環境中,檢查鍵的類型是否一致
          checkIdentityKeys(target, has, key)
        }
      
        // 獲取舊值
        const oldValue = get.call(target, key)
        // 設置新值
        target.set(key, value)
      
        // 觸發依賴追蹤
        if (!hadKey) {
          // 如果鍵之前不存在,觸發添加操作的依賴
          trigger(target, TriggerOpTypes.ADD, key, value)
        } else if (hasChanged(value, oldValue)) {
          // 如果鍵之前存在且值發生了變化,觸發設置操作的依賴
          trigger(target, TriggerOpTypes.SET, key, value, oldValue)
        }
      
        // 返回 this 以支持鏈式調用
        return this
      }
      
      
      posted @ 2024-07-30 23:43  feixianxing  閱讀(226)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 日韩av熟女人妻一区二| 久久久久青草线综合超碰| 国产v综合v亚洲欧美久久| 国产精品综合av一区二区| 人人妻人人狠人人爽天天综合网| 久久亚洲2019中文字幕| 麻豆蜜桃av蜜臀av色欲av| 波多野结衣一区二区免费视频| 国产色悠悠综合在线观看 | 性欧美丰满熟妇xxxx性| 国产精品一区在线蜜臀| 久久久久综合一本久道| 国产老妇伦国产熟女老妇高清| 亚洲av色一区二区三区| 亚洲 一区二区 在线| 在线中文字幕国产一区| 成人免费AA片在线观看 | 亚洲国产成人无码影片在线播放| 日本人妻巨大乳挤奶水免费| 任你躁国产自任一区二区三区| 亚洲av永久无码精品水牛影视| bt天堂新版中文在线| 欧洲免费一区二区三区视频| 精品亚洲欧美无人区乱码| 国产精品一区在线蜜臀| 最新午夜男女福利片视频| 国产中文字幕在线一区| 彰化市| 老司机亚洲精品一区二区| 国产资源精品中文字幕| 老少配老妇老熟女中文普通话| 欧美另类videossexo高潮| 欧美老少配性行为| 亚洲日本欧洲二区精品| 久女女热精品视频在线观看| 做暖暖视频在线看片免费| 蜜桃成人无码区免费视频网站| 亚洲成aⅴ人在线观看| 亚洲一区精品视频在线| 日本电影一区二区三区| 成人午夜av在线播放|