Vue3 源碼解析(六):響應(yīng)式原理與 reactive

今天這篇文章是筆者會帶著大家一起深入剖析 Vue3 的響應(yīng)式原理實(shí)現(xiàn),以及在響應(yīng)式基礎(chǔ) API 中的 reactive 是如何實(shí)現(xiàn)的。對于 Vue 框架來說,其非侵入的響應(yīng)式系統(tǒng)是最獨(dú)特的特性之一了,所以不論任何一個(gè)版本的 Vue,在熟悉其基礎(chǔ)用法后,響應(yīng)式原理都是筆者最想優(yōu)先了解的部分,也是閱讀源碼時(shí)必細(xì)細(xì)研究的部分。畢竟知己知彼百戰(zhàn)不殆,當(dāng)你使用 Vue 時(shí),掌握了響應(yīng)式原理一定會讓你的 coding 過程更加游刃有余的。

Vue2 的響應(yīng)式原理

在開始介紹 Vue3 的響應(yīng)式原理前,我們先一起回顧一下 Vue2 的響應(yīng)式原理。

當(dāng)我們把一個(gè)普通選項(xiàng)傳入 Vue 實(shí)例的 data 選項(xiàng)中,Vue 將遍歷此對象所有的 property,并使用 Object.defineProperty 把這些 property 全部轉(zhuǎn)為 getter/setter。而 Vue2 在處理數(shù)組時(shí),也會通過原型鏈劫持會改變數(shù)組內(nèi)元素的方法,并在原型鏈觀察新增的元素,以及派發(fā)更新通知。

vue2-observer

這里放上一張 Vue2 文檔中介紹響應(yīng)式的圖片。對于文檔中有的描述筆者就不再贅述,而從 Vue2 的源碼角度來對照圖片說一說。在 Vue2 的源碼中的 src/core 路徑下有一個(gè) observer 模塊,它就是 Vue2 中處理響應(yīng)式的地方了。在這個(gè)模塊下 observer 負(fù)責(zé)將對象、數(shù)組轉(zhuǎn)換成響應(yīng)式的,即圖中的紫色部分,處理 Data 的 getter 及 setter。當(dāng) data 中的選項(xiàng)被訪問時(shí),會觸發(fā) getter,此時(shí) observer 目錄下的 wather.js 模塊就會開始工作,它的任務(wù)就是收集依賴,我們收集到的依賴是一個(gè)個(gè) Dep 類的實(shí)例化對象。而 data 中的選項(xiàng)變更時(shí),會觸發(fā) setter 的調(diào)用,而在 setter 的過程中,觸發(fā) dep 的 notify 函數(shù),派發(fā)更新事件,由此實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)監(jiān)聽。

Vue3 的響應(yīng)式變化

在簡單回顧了 Vue2 的響應(yīng)式原理后,我們會有一個(gè)疑惑,Vue3 的響應(yīng)式原理與 Vue2 相比有什么不同呢?

在 Vue3 中響應(yīng)式系統(tǒng)最大的區(qū)別就是,數(shù)據(jù)模型是被代理的 JavaScript 對象了。不論是我們在組件的 data 選項(xiàng)中返回一個(gè)普通的JavaScript 對象,還是使用 composition api 創(chuàng)建一個(gè) reactive 對象,Vue3 都會將該對象包裹在一個(gè)帶有 get 和 set 處理程序的 Proxy 中。

Proxy 對象用于創(chuàng)建一個(gè)對象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值等)。

其基礎(chǔ)語法類似于:

const p = new Proxy(target, handler)

Proxy 相比較于 Object.defineProperty 究竟有什么優(yōu)勢呢?這個(gè)問題讓我們先從 Object.defineProperty 的弊端說起。

從 Object 的角度來說,由于 Object.defineProperty 是對指定的 key 生成 getter/setter 以進(jìn)行變化追蹤,那么如果這個(gè) key 一開始不存在我們定義的對象上,響應(yīng)式系統(tǒng)就無能為力了,所以在 Vue2 中無法檢測對象的 property 的添加或移除。而對于這個(gè)缺陷,Vue2 提供了 vm.$set 和全局的 Vue.set API 讓我們能夠向?qū)ο筇砑禹憫?yīng)式的 property。

從數(shù)組的角度來說,當(dāng)我們直接利用索引設(shè)置一個(gè)數(shù)組項(xiàng)時(shí),或者當(dāng)我們修改數(shù)組長度時(shí),Vue2 的響應(yīng)式系統(tǒng)都不能監(jiān)聽到變化,解決的方法也如上,使用上面提及的 2 個(gè) api。

而這些問題在 ES6 的新特性 Proxy 面前通通都是不存在的,Proxy 對象能夠利用 handler 陷阱在 get、set 時(shí)捕獲到任何變動(dòng),也能監(jiān)聽對數(shù)組索引的改動(dòng)以及 數(shù)組 length 的改動(dòng)。

而依賴收集和派發(fā)更新的方式在 Vue3 中也變得不同,在這里我先快速的整體描述一下:在 Vue3 中,通過 track 的處理器函數(shù)來收集依賴,通過 trigger 的處理器函數(shù)來派發(fā)更新,每個(gè)依賴的使用都會被包裹到一個(gè)副作用(effect)函數(shù)中,而派發(fā)更新后就會執(zhí)行副作用函數(shù),這樣依賴處的值就被更新了。

響應(yīng)式基礎(chǔ) reactive 的實(shí)現(xiàn)

既然這是一個(gè)源碼分析的文章,咱們還是從源碼的角度來分析響應(yīng)式究竟是如何實(shí)現(xiàn)的。所以筆者會先分析響應(yīng)式基礎(chǔ)的 API —— reactive ,相信通過講解 reactive 的實(shí)現(xiàn),大家會對 Proxy 有更深刻的認(rèn)識。

reactive

二話不說,直接看源碼。下面是 reactive API 的函數(shù),函數(shù)的參數(shù)接受一個(gè)對象,通過 createReactiveObject 函數(shù)處理后,直接返回一個(gè) proxy 對象。

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // 如果試圖去觀察一個(gè)只讀的代理對象,會直接返回只讀版本
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  // 創(chuàng)建一個(gè)代理對象并返回
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

在第三行能看到通過判斷 target 中是否有 ReactiveFlags 中的 IS_READONLY key 確定對象是否為只讀對象。ReactiveFlags 枚舉會在源碼中不斷的與我們見面,所以有必要提前介紹一下 ReactiveFlags:

export const enum ReactiveFlags {
  SKIP = '__v_skip', // 是否跳過響應(yīng)式 返回原始對象
  IS_REACTIVE = '__v_isReactive', // 標(biāo)記一個(gè)響應(yīng)式對象
  IS_READONLY = '__v_isReadonly', // 標(biāo)記一個(gè)只讀對象
  RAW = '__v_raw' // 標(biāo)記獲取原始值
}

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

createReactiveObject

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

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

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

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

接著我們開始看 createReactiveObject 的邏輯部分:

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  // 如果目標(biāo)不是對象,直接返回原始值
  if (!isObject(target)) {
    return target
  }
  // 如果目標(biāo)已經(jīng)是一個(gè)代理,直接返回
  // 除非對一個(gè)響應(yīng)式對象執(zhí)行 readonly
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // 目標(biāo)已經(jīng)存在對應(yīng)的代理對象
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // 只有白名單里的類型才能被創(chuàng)建響應(yīng)式對象
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

在該函數(shù)的邏輯部分,可以看到基礎(chǔ)數(shù)據(jù)類型并不會被轉(zhuǎn)換成代理對象,而是直接返回原始值。

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

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

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

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

Handlers 的組成

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

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

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

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

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

get 陷阱

上一段提到 get 是由一個(gè)工廠函數(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 個(gè)類型,分別對應(yīng)不同的響應(yīng)式 API,從名稱中就可以知道對應(yīng)的 API 名稱,非常一目了然。而所有的 get 都是由 createGetter 函數(shù)生成的。所以接下來我們著重看一下 createGetter 的邏輯。

還是老規(guī)矩,先從函數(shù)簽名看起。

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {}
}

createGetter 有 isReadonly 和 shallow 兩個(gè)參數(shù),讓使用 get 陷阱的 api 按需使用。而函數(shù)的內(nèi)部返回了一個(gè) get 函數(shù),使用高階函數(shù)的方式返回將會傳入 handlers 中 get 參數(shù)的函數(shù)。

接著看 createGetter 的邏輯:

// 如果 get 訪問的 key 是 '__v_isReactive',返回 createGetter 的 isReadonly 參數(shù)取反結(jié)果
if (key === ReactiveFlags.IS_REACTIVE) {
  return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
  // 如果 get 訪問的 key 是 '__v_isReadonly',返回 createGetter 的 isReadonly 參數(shù)
  return isReadonly
} else if (
  // 如果 get 訪問的 key 是 '__v_raw',并且 receiver 與原始標(biāo)識相等,則返回原始值
  key === ReactiveFlags.RAW &&
  receiver ===
    (isReadonly
      ? shallow
        ? shallowReadonlyMap
        : readonlyMap
      : shallow
        ? shallowReactiveMap
        : reactiveMap
    ).get(target)
) {
  return target
}

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

接著看 get 的后續(xù)邏輯。

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

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

我們之前說過 Vue2 以原型鏈的方式劫持了數(shù)組,而在這里也有類似地作用,而數(shù)組的部分我們準(zhǔn)備放在后續(xù)的文章中再介紹,下面是需要特殊處理的數(shù)組。

  • 對索引敏感的數(shù)組方法
    • includes、indexOf、lastIndexOf
  • 會改變自身長度的數(shù)組方法,需要避免 length 被依賴收集,因?yàn)檫@樣可能會造成循環(huán)引用
    • push、pop、shift、unshift、splice
// 判斷 taeget 是否是數(shù)組
const targetIsArray = isArray(target)
// 如果不是只讀對象,并且目標(biāo)對象是個(gè)數(shù)組,訪問的 key 又在數(shù)組需要劫持的方法里,直接調(diào)用修改后的數(shù)組方法執(zhí)行
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
  return Reflect.get(arrayInstrumentations, key, receiver)
}

// 獲取 Reflect 執(zhí)行的 get 默認(rèn)結(jié)果
const res = Reflect.get(target, key, receiver)

// 如果是 key 是 Symbol,并且 key 是 Symbol 對象中的 Symbol 類型的 key
// 或者 key 是不需要追蹤的 key: __proto__,__v_isRef,__isVue
// 直接返回 get 結(jié)果
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
  return res
}

// 不是只讀對象,執(zhí)行 track 收集依賴
if (!isReadonly) {
  track(target, TrackOpTypes.GET, key)
}

// 如果是 shallow 淺層響應(yīng)式,直接返回 get 結(jié)果
if (shallow) {
  return res
}

if (isRef(res)) {
  // 如果是 ref ,則返回解包后的值 - 當(dāng) target 是數(shù)組,key 是 int 類型時(shí),不需要解包
  const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
  return shouldUnwrap ? res.value : res
}

if (isObject(res)) {
  // 將返回的值也轉(zhuǎn)換成代理,我們在這里做 isObject 的檢查以避免無效值警告。
  // 也需要在這里惰性訪問只讀和星影視對象,以避免循環(huán)依賴。
  return isReadonly ? readonly(res) : reactive(res)
}

// 不是 object 類型則直接返回 get 結(jié)果
return res

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

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

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

  • __proto__
  • _v_isRef
  • __isVue

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

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

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

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

當(dāng) res 都不滿足上述條件時(shí),直接返回 res 結(jié)果。例如基礎(chǔ)數(shù)據(jù)類型就會直接返回結(jié)果,而不做特殊處理。

至此,get 陷阱的邏輯全部結(jié)束了。

set 陷阱

與 createGetter 對應(yīng),set 也有一個(gè) createSetter 的工廠函數(shù),也是通過柯里化的方式返回一個(gè) set 函數(shù)。

函數(shù)簽名都大同小異,那么接下來筆者直接帶大家盤邏輯。

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

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    if (!shallow) {
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      // 當(dāng)不是 shallow 模式時(shí),判斷舊值是否是 Ref,如果是則直接更新舊值的 value
      // 因?yàn)?ref 有自己的 setter
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // shallow 模式不需要特殊處理,對象按原樣 set
    }
        
    // 判斷 target 中是否存在 key
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // Reflect.set 獲取默認(rèn)行為的返回值
    const result = Reflect.set(target, key, value, receiver)
    // 如果目標(biāo)是原始對象原型鏈上的屬性,則不會觸發(fā) trigger 派發(fā)更新
    if (target === toRaw(receiver)) {
      // 使用 trigger 派發(fā)更新,根據(jù) hadKey 區(qū)別調(diào)用事件
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

在 set 的過程中會首先獲取新舊與舊值,當(dāng)目前的代理對象不是淺層比較時(shí),會判斷舊值是否是一個(gè) Ref,如果舊值不是數(shù)組且是一個(gè) ref類型的對象,并且新值不是 ref 對象時(shí),會直接修改舊值的 value。

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

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

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

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

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

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

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

總結(jié)

在今天的文章中,筆者先帶大家回顧了 Vue2 的響應(yīng)式原理,又開始介紹 Vue3 的響應(yīng)式原理,通過比較 Vue2 和 Vue3 的響應(yīng)式系統(tǒng)的區(qū)別引出 Vue3 響應(yīng)式系統(tǒng)的提升之處,尤其是其中最主要的調(diào)整將 Object.defineProperty 替換為 Proxy 代理對象。

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

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

最后,如果這篇文章能夠幫助到你了解 Vue3 中的響應(yīng)式原理和 reactive 的實(shí)現(xiàn),希望能給本文點(diǎn)一個(gè)喜歡??。如果想繼續(xù)追蹤后續(xù)文章,也可以關(guān)注我的賬號或 follow 我的 github,再次謝謝各位可愛的看官老爺。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容