聽說你很瞭解 Vue3 響應式?

前言

【A】:能不能說說 Vue3 響應式都處理了哪些數據類型?都怎麼處理的呀?

【B】:能,只能說一點點...

【A】:...

只要問到 Vue 相關的內容,似乎總繞不過 響應式原理 的話題,隨之而來的回答必然是圍繞着 Object.defineProperty 和 Proxy 來展開(即 Vue2 和 Vue3),但若繼續追問某些具體實現是不是就倉促結束回答了(你跑我追,你不跑我還追 )。

本文就不再過多介紹 Vue2 中響應式的處理,但是會有簡單提及,下面就來看看 Vue3 中是如何處理 原始值、Object、Array、Set、Map 等數據類型的響應式。

從 Object.defineProperty 到 Proxy

一切的一切還得從 Object.defineProperty 開始講起,那是一個不一樣的 API ... (bgm 響起,自行體會)

Object.defineProperty

Object.defineProperty(obj, prop, descriptor) 方法會直接在一個對象上定義一個 新屬性,或修改一個 對象 的 現有屬性,並返回此對象,其參數具體爲:

從以上的描述就可以看出一些限制,比如:

在 Vue2 中的缺陷

Object.defineProperty() 實際是通過 定義 或 修改 對象屬性 的描述符來實現 數據劫持,其對應的缺點也是沒法被忽略的:

【擴展】Object.defineProperty 和 Array ?

它們有啥關係,其實沒有啥關係,只是大家習慣性的會回答 Object.defineProperty 不能攔截 Array 的操作,這句話說得對但也不對。

使用 Object.defineProperty 攔截 Array

Object.defineProperty 可用於實現對象屬性的 get 和 set 攔截,而數組其實也是對象,那自然是可以實現對應的攔截操作,如下:

Vue2 爲什麼不使用 Object.defineProperty 攔截 Array?

尤大在曾在 GitHub 的 Issue 中做過如下回復:

說實話性能問題到底指的是什麼呢?下面是總結了一些目前看到過的回答:

Proxy & Reflect

由於在 Vue2 中使用 Object.defineProperty 帶來的缺陷,導致在 Vue2 中不得不提供了一些額外的方法(如:Vue.set、Vue.delete())解決問題,而在 Vue3 中使用了 Proxy 的方式來實現 數據劫持,而上述的問題在 Proxy 中都可以得到解決。

Proxy

Proxy 主要用於創建一個 對象的代理,從而實現基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數調用等),本質上是通過攔截對象 內部方法 的執行實現代理,而對象本身根據規範定義的不同又會區分爲 常規對象 和 異質對象(這不是重點,可自行了解)。

Reflect

Reflect 是一個內置的對象,它提供攔截 JavaScript 操作的方法,這些方法與 Proxy handlers 提供的的方法是一一對應的,且 Reflect 不是一個函數對象,即不能進行實例化,其所有屬性和方法都是靜態的。

Proxy 爲什麼需要 Reflect 呢?

在 Proxy 的 get(target, key, receiver)、set(target, key, newVal, receiver) 的捕獲器中都能接到前面所列舉的參數:

怎麼理解 Proxy handler 中 receiver 指向的是當前操作正確上的下文呢?

在 Reflect 的方法中通常只需要傳遞 target、key、newVal 等,但爲了能夠處理上述提到的特殊情況,一般也需要傳遞 receiver 參數,因爲 Reflect 方法中傳遞的 receiver 參數代表執行原始操作時的 this 指向,比如:Reflect.get(target, key , receiver)Reflect.set(target, key, newVal, receiver)

總結Reflect 是爲了在執行對應的攔截操作的方法時能 傳遞正確的 this 上下文

Vue3 如何使用 Proxy 實現數據劫持?

Vue3 中提供了 reactive() 和 ref() 兩個方法用來將 目標數據 變成 響應式數據,而通過 Proxy 來實現 數據劫持(或代理) 的具體實現就在其中,下面一起來看看吧!

reactive 函數

從源碼來看,其核心其實就是 createReactiveObject(...) 函數,那麼繼續往下查看對應的內容

源碼位置:packages\reactivity\src\reactive.ts

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  // 若目標對象是響應式的只讀數據,則直接返回
  if (isReadonly(target)) {
    return target
  }

  // 否則將目標數據嘗試變成響應式數據
  return createReactiveObject(
    target,
    false,
    mutableHandlers, // 對象類型的 handlers
    mutableCollectionHandlers, // 集合類型的 handlers
    reactiveMap
  )
}

createReactiveObject() 函數

源碼的體現也是非常簡單,無非就是做一些前置判斷處理:

看來具體的實現又在不同數據類型的 捕獲器 中,即下面源碼的 collectionHandlers 和 baseHandlers ,而它們則對應的是在上述 reactive() 函數中爲 createReactiveObject() 函數傳遞的 mutableCollectionHandlers 和 mutableHandlers 參數。

源碼位置:packages\reactivity\src\reactive.ts

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {

  // 非對象類型直接返回
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }

  // 目標數據的 __v_raw 屬性若爲 true,且是【非響應式數據】或 不是通過調用 readonly() 方法,則直接返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }

  // 目標對象已存在相應的 proxy 代理對象,則直接返回
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

  // 只有在白名單中的值類型纔可以被代理監測,否則直接返回
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target     
  }

  // 創建代理對象
  const proxy = new Proxy(
    target,
    // 若目標對象是集合類型(Set、Map)則使用集合類型對應的捕獲器,否則使用基礎捕獲器
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers 
  )

  // 將對應的代理對象存儲在 proxyMap 中
  proxyMap.set(target, proxy)

  return proxy
}

捕獲器 Handlers

對象類型的捕獲器 — mutableHandlers

這裏的對象類型指的是 數組 和 普通對象

源碼位置:packages\reactivity\src\baseHandlers.ts

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

以上這些捕獲器其實就是我們在上述 Proxy 部分列舉出來的捕獲器,顯然可以攔截對普通對象的如下操作:

get 捕獲器

具體信息在下面的註釋中,這裏只列舉核心內容:

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 當直接通過指定 key 訪問 vue 內置自定義的對象屬性時,返回其對應的值
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }

    // 判斷是否爲數組類型
    const targetIsArray = isArray(target)

    // 數組對象
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      // 重寫/增強數組的方法: 
      //  - 查找方法:includes、indexOf、lastIndexOf
      //  - 修改原數組的方法:push、pop、unshift、shift、splice
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    // 獲取對應屬性值
    const res = Reflect.get(target, key, receiver)

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // 依賴收集
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 淺層響應
    if (shallow) {
      return res
    }

    // 若是 ref 類型響應式數據,會進行【自動脫 ref】,但不支持【數組】+【索引】的訪問方式
    if (isRef(res)) {
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    // 屬性值是對象類型:
    //  - 是隻讀屬性,則通過 readonly() 返回結果,
    //  - 且是非只讀屬性,則遞歸調用 reactive 向外返回 proxy 代理對象
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

set 捕獲器

除去額外的邊界處理,其實核心還是 更新屬性值,並通過 trigger(...) 觸發依賴更新

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    // 保存舊的數據
    let oldValue = (target as any)[key]

    // 若原數據值屬於 只讀 且 ref 類型,並且新數據值不屬於 ref 類型,則意味着修改失敗
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      return false
    }

    if (!shallow && !isReadonly(value)) {
      if (!isShallow(value)) {
        value = toRaw(value)
        oldValue = toRaw(oldValue)
      }
      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
    }

    // 是否存在對應的 key
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)

    // 設置對應值
    const result = Reflect.set(target, key, value, receiver)

    // 若目標對象是原始原型鏈上的內容(非自定義添加),則不觸發依賴更新
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        // 目標對象不存在對應的 key,則爲新增操作
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        // 目標對象存在對應的值,則爲修改操作
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }

    // 返回修改結果
    return result
  }
}

deleteProperty & has & ownKeys 捕獲器

這三個捕獲器內容非常簡潔,其中 has 和 ownKeys 本質也屬於 讀取操作,因此需要通過 track() 進行依賴收集,而 deleteProperty 相當於修改操作,因此需要 trigger() 觸發更新

function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key)
  const oldValue = (target as any)[key]
  const result = Reflect.deleteProperty(target, key)
  // 目標對象上存在對應的 key ,並且能成功刪除,纔會觸發依賴更新
  if (result && hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}

function has(target: object, key: string | symbol): boolean {
  const result = Reflect.has(target, key)
  if (!isSymbol(key) || !builtInSymbols.has(key)) {
    track(target, TrackOpTypes.HAS, key)
  }
  return result
}

function ownKeys(target: object)(string | symbol)[] {
  track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
  return Reflect.ownKeys(target)
}

數組類型捕獲器 —— arrayInstrumentations

數組類型 和 對象類型 的大部分操作是可以共用的,比如 obj.name 和 arr[index] 等,但數組類型的操作還是會比對象類型更豐富一些,而這些就需要特殊處理。

源碼位置:packages\reactivity\src\collectionHandlers.ts

處理數組索引 index 和 length

數組的 index 和 length 是會相互影響的,比如存在數組 const arr = [1] :

爲了能夠合理觸發和 length 相關副作用函數的執行,在 set() 捕獲器中會判斷當前操作的類型:

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
  
   省略其他代碼
   
    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
    
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

處理數組的查找方法

數組的查找方法包括 includesindexOflastIndexOf,這些方法通常情況下是能夠按預期進行工作,但還是需要對某些特殊情況進行處理:

源碼位置:packages\reactivity\src\baseHandlers.ts

;(['includes''indexOf''lastIndexOf'] as const).forEach(key ={
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      // 外部調用上述方法,默認其內的 this 指向的是代理數組對象,
      // 但實際上是需要通過原始數組中進行遍歷查找
      const arr = toRaw(this) as any
      for (let i = 0, l = this.length; i < l; i++) {
        track(arr, TrackOpTypes.GET, i + '')
      }
      // we run the method using the original args first (which may be reactive)
      const res = arr[key](...args)
      if (res === -1 || res === false) {
        // if that didn't work, run it again using raw values.
        return arr[key](...args.map(toRaw))
      } else {
        return res
      }
    }
  })

處理數組影響 length 的方法

隱式修改數組長度的原型方法包括 pushpopshiftunshiftsplice 等,在調用這些方法的同時會間接的讀取數組的 length 屬性,又因爲這些方法具有修改數組長度的能力,即相當於 length 的設置操作,若不進行特殊處理,會導致與 length 屬性相關的副作用函數被重複執行,即 棧溢出,比如:

const proxy = reactive([])

// 第一個副作用函數
effect(() ={
  proxy.push(1) // 讀取 + 設置 操作
})

// 第二個副作用函數
effect(() ={
  proxy.push(2) // 讀取 + 設置 操作(此時進行 trigger 時,會觸發包括第一個副作用函數的內容,然後循環導致棧溢出)
})

在源碼中還是通過 重寫 / 增強 上述對應數組方法的形式實現自定義的邏輯處理:

源碼位置:packages\reactivity\src\baseHandlers.ts

;(['push''pop''shift''unshift''splice'] as const).forEach(key ={
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      pauseTracking()
      const res = (toRaw(this) as any)[key].apply(this, args)
      resetTracking()
      return res
    }
  })

集合類型的捕獲器 — mutableCollectionHandlers

集合類型 包括 MapWeakMapSetWeakSet 等,而對 集合類型 的 代理模式 和 對象類型 需要有所不同,因爲 集合類型 和 對象類型 的操作方法是不同的,比如:

Map 類型 的原型 屬性 和 方法 如下:

Set 類型 的原型 屬性 和 方法 如下:

源碼位置:packages\reactivity\src\collectionHandlers.ts

解決 代理對象 無法訪問 集合類型 對應的 屬性 和 方法

代理集合類型的第一個問題,就是代理對象沒法獲取到集合類型的屬性和方法,比如:

從報錯信息可以看出 size 屬性是一個訪問器屬性,所以它被作爲方法調用了,而主要錯誤原因就是在這個訪問器中的 this 指向的是 代理對象,在源碼中就是通過爲這些特定的 屬性 和 方法 定義對應的 key 的 mutableInstrumentations 對象,並且在其對應的 屬性 和 方法 中將 this指向爲 原對象.

function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
  const target = (this as any)[ReactiveFlags.RAW]
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)
  if (key !== rawKey) {
    !isReadonly && track(rawTarget, TrackOpTypes.HAS, key)
  }
  !isReadonly && track(rawTarget, TrackOpTypes.HAS, rawKey)
  return key === rawKey
    ? target.has(key)
    : target.has(key) || target.has(rawKey)
}

function size(target: IterableCollections, isReadonly = false) {
  target = (target as any)[ReactiveFlags.RAW]
  !isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
  return Reflect.get(target, 'size', target)
}

省略其他代碼

處理集合類型的響應式

集合建立響應式核心還是 track 和 trigger,轉而思考的問題就變成,什麼時候需要 track、什麼時候需要 trigger:

這裏涉及一些優化的內容,比如:

function createInstrumentations() {
  const mutableInstrumentations: Record<string, Function> = {
    get(this: MapTypes, key: unknown) {// track
      return get(this, key)
    },
    get size() {// track
      return size(this as unknown as IterableCollections)
    },
    has,// track
    add,// trigger
    set,// trigger
    delete: deleteEntry,// trigger
    clear,// trigger
    forEach: createForEach(false, false) // track
  }
  省略其他代碼
}

避免污染原始數據

通過重寫集合類型的方法並手動指定其中的 this 指向爲 原始對象 的方式,解決 代理對象 無法訪問 集合類型 對應的 屬性 和 方法 的問題,但這樣的實現方式也帶來了另一個問題:原始數據被污染

簡單來說,我們只希望 代理對象(響應式對象 才具備 依賴收集 (track) 和 依賴更新 (trigger) 的能力,而通過 原始數據 進行的操作不應該具有響應式的能力。

如果只是單純的把所有操作直接作用到 原始對象 上就不能保證這個結果,比如:

 // 原數數據 originalData1
  const originalData1 = new Map({});
  // 代理對象 proxyData1
  const proxyData1 = reactive(originalData1);

  // 另一個代理對象 proxyData2
  const proxyData2 = reactive(new Map({}));

  // 將 proxyData2 做爲 proxyData1 一個鍵值
  // 【注意】此時的 set() 經過重寫,其內部 this 已經指向 原始對象(originalData1),等價於 原始對象 originalData1 上存儲了一個 響應式對象 proxyData2
  proxyData1.set("proxyData2", proxyData2);

  // 若不做額外處理,如下基於 原始數據的操作 就會觸發 track 和 trigger
  originalData1.get("proxyData2").set("name""zs");

在源碼中的解決方案也是很簡單,直接通過 value = toRaw(value) 獲取當前設置值對應的 原始數據,這樣舊可以避免 響應式數據對原始數據的污染

處理 forEach 回調參數

首先 Map.prototype.forEach(callbackFn [, thisArg\])  其中 callbackFn 回調函數會接收三個參數:

遍歷操作 等價於 讀取操作,在處理 普通對象 的 get() 捕獲器中有一個處理,如果當前訪問的屬性值是 對象類型 那麼就會向外返回其對應的 代理對象,目的是實現 惰性響應 和 深層響應,這個處理也同樣適用於 集合類型

因此,在源碼中通過 callback.call(thisArg, wrap(value), wrap(key), observed) 的方式將 Map 類型的  和  進行響應式處理,以及進行 track 操作,因爲 Map 類型關注的就是  和 

function createForEach(isReadonly: boolean, isShallow: boolean) {
  return function forEach(
    this: IterableCollections,
    callback: Function,
    thisArg?: unknown
  ) {
    const observed = this as any
    const target = observed[ReactiveFlags.RAW]
    const rawTarget = toRaw(target)
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    !isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
    return target.forEach((value: unknown, key: unknown) ={
      // important: make sure the callback is
      // 1. invoked with the reactive map as `this` and 3rd arg
      // 2. the value received should be a corresponding reactive/readonly.
      return callback.call(thisArg, wrap(value), wrap(key), observed)
    })
  }

處理迭代器

集合類型的迭代器方法:

Map 和 Set 都實現了 可迭代協議(即 Symbol.iterator 方法,而 迭代器協議 是指 一個對象實現了 next 方法),因此它們還可以通過 for...of 的方式進行遍歷。

根據對 forEach 的處理,不難知道涉及遍歷的方法,終究還是得將其對應的遍歷的 鍵、值 進行響應式包裹的處理,以及進行 track 操作,而原本的的迭代器方法沒辦法實現,因此需要內部自定義迭代器協議。

const iteratorMethods = ['keys''values''entries', Symbol.iterator]
  iteratorMethods.forEach(method ={
    mutableInstrumentations[method as string] = createIterableMethod(
      method,
      false,
      false
    )
    省略其他代碼
  })

這一部分的源碼涉及的內容比較多,以上只是簡單的總結一下,更詳細的內容可查看對應的源碼內容。

ref 函數 — 原始值的響應式

原始值指的是 Boolean、Number、BigInt、String、Symbol、undefined、null 等類型的值,我們知道用 Object.defineProperty 肯定是不支持,因爲它攔截的就是對象屬性的操作,都說 Proxy 比 Object.defineProperty 強,那麼它能不能直接支持呢?

直接支持是肯定不能的,別忘了 Proxy 代理的目標也還是對象類型呀,它的強是在自己的所屬領域,跨領域也是遭不住的。

因此在 Vue3 的 ref 函數中對原始值的處理方式是通過爲 原始值類型 提供一個通過 new RefImpl(rawValue, shallow) 實例化得到的 包裹對象,說白了還是將原始值類型變成對象類型,但 ref 函數的參數並 不限制數據類型

源碼位置:packages\reactivity\src\ref.ts

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

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly __v_isShallow: boolean) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    // 將依賴收集到 dep 中,實際上就是一個 Set 類型
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    // 獲取原始數據
    newVal = this.__v_isShallow ? newVal : toRaw(newVal)

    // 通過 Object.is(value, oldValue) 判斷新舊值是否一致,若不一致才需要進行更新
    if (hasChanged(newVal, this._rawValue)) {
      // 保存原始值
      this._rawValue = newVal
      // 更新爲新的 value 值
      this._value = this.__v_isShallow ? newVal : toReactive(newVal)
      // 依賴更新,從 dep 中取出對應的 effect 函數依次遍歷執行
      triggerRefValue(this, newVal)
    }
  }
}

// 若當前 value 是 對象類型,纔會通過 reactive 轉換爲響應式數據
export const toReactive = <T extends unknown>(value: T)T =>
  isObject(value) ? reactive(value) : value

Vue3 如何進行依賴收集?

在 Vue2 中依賴的收集方式是通過 Dep 和 Watcher 的 觀察者模式 來實現的,是不是還能想起初次瞭解 Dep 和 Watcher 之間的這種 剪不斷理還亂 的關係時的心情 ......

依賴收集 其實說的就是 track 函數需要處理的內容:

可視化結構如下:

源碼位置:packages\reactivity\src\effect.ts

const targetMap = new WeakMap<any, KeyToDepMap>()

export function track(target: object, type: TrackOpTypes, key: unknown) {
  // 當前應該進行依賴收集 且 有對應的副作用函數時,纔會進行依賴收集
  if (shouldTrack && activeEffect) {
    // 從容器中取出【對應響應式數據對象】的依賴關係
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      // 若不存在,則進行初始化
      targetMap.set(target, (depsMap = new Map()))
    }

    // 獲取和對【應響應式數據對象 key】相匹配的依賴
    let dep = depsMap.get(key)
    if (!dep) {
      // 若不存在,則進行初始化 dep 爲 Set 實例
      depsMap.set(key, (dep = createDep()))
    }

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined

    // 往 dep 集合中添加 effect 依賴
    trackEffects(dep, eventInfo)
  }
}

export const createDep = (effects?: ReactiveEffect[])Dep ={
  const dep = new Set<ReactiveEffect>(effects) as Dep
  dep.w = 0
  dep.n = 0
  return dep
}

最後

以上就是針對 Vue3 中對不同數據類型的處理的內容,無論是 Vue2 還是 Vue3 響應式的核心都是 數據劫持 / 代理、依賴收集、依賴更新,只不過由於實現數據劫持方式的差異從而導致具體實現的差異,在 Vue3 中值得注意的是:

作者:熊的貓

https://juejin.cn/post/7147461004954173471

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/2MIrf5fJW2rXHHC6xt_tyA