Vue3.2 響應(yīng)式原理源碼剖析,及與 Vue2 .x響應(yīng)式的區(qū)別

本文源碼版本 Vue3.2.11,Vue2 響應(yīng)式源碼剖析點(diǎn)這里 深入淺出 Vue2 響應(yīng)式原理源碼剖析

我們知道相較 Vue2.x 的響應(yīng)式 Vue3 對(duì)整個(gè)響應(yīng)式都做了重大升級(jí);然后 Vue3.2 相較 3.0 版本源碼又做了許多變更,一起來看看吧

Vue3 和 Vue2 響應(yīng)式區(qū)別

響應(yīng)式性能的提升

根據(jù)8月10號(hào)尤大發(fā)布 Vue3.2 說明原文 得知:

  • 更高效的 ref 實(shí)現(xiàn),讀取提升約 260%,寫入提升約 50%
  • 依賴收集速度提升約 40%
  • 減少內(nèi)存消耗約 17%

使用上的區(qū)別

Vue2 中只要寫在組件中 data 函數(shù)返回的對(duì)象里的屬性 自動(dòng)就有響應(yīng)式

Vue3 則是通過 ref 定義普通類型響應(yīng)式和 reactive 定義復(fù)雜類型響應(yīng)式數(shù)據(jù)

<script setup>
  import { ref, reactive, toRefs } from "vue"
  const name = ref('沐華')
  const obj = reactive({ name: '沐華' })
  const data = { ...toRefs(obj) }
</script>

擴(kuò)展:通過 toRefs 可以把響應(yīng)式對(duì)象轉(zhuǎn)為普通對(duì)象
因?yàn)槭褂?reactive 定義的響應(yīng)式對(duì)象在進(jìn)行解構(gòu)(展開)或者銷毀的時(shí)候,響應(yīng)式就會(huì)失效了,因?yàn)?reactive 實(shí)例下有很多屬性,解構(gòu)就丟失了,所以在需要解構(gòu)且保持響應(yīng)式的時(shí)候就可以用 toRefs

源碼目錄結(jié)構(gòu)區(qū)別

Vue2 響應(yīng)式的源碼核心部分在 src/core/observer 這個(gè)目錄,但是里面也引入了很多其他目錄東西,不獨(dú)立,耦合度比較高

Vue3 響應(yīng)式源碼全部在 packages/reactivity 這個(gè)目錄下,不涉及其他任何地方,功能獨(dú)立,而且單獨(dú)發(fā)布成 npm 包,可以集成進(jìn)其他框架

原理上的區(qū)別

我們知道在 Vue2 中使用 Object.defineProperty 實(shí)現(xiàn)響應(yīng)式對(duì)象,而這種方式是存在一些缺陷的

  • 基于屬性攔截,初時(shí)化時(shí)會(huì)遞歸全部屬性,對(duì)性能有一定影響,并且后續(xù)給對(duì)象中添加的新屬性,無(wú)法觸發(fā)響應(yīng)式,對(duì)應(yīng)的解決辦法是通過 Vue.set() 方法來添加新屬性
  • 無(wú)法檢測(cè)到數(shù)組內(nèi)部變化,對(duì)應(yīng)的解決方法是通過重寫了7個(gè)會(huì)改變?cè)瓟?shù)組的方法

而在 Vue3 中則是用 Proxy 進(jìn)行重構(gòu),完全取代了 defineProperty,因?yàn)?Proxy 可以劫持整個(gè)對(duì)象,就不存在上述問題了

那么是如何解決這些問題的呢?

對(duì)象

先看下 Vue2 在首次渲染時(shí)的響應(yīng)式處理,源碼地址:src/core/observer/index.js - 157行

...
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () { ... },
    set: function reactiveSetter (newVal) { ... }
})
...

由參數(shù)可以看出,它是需要根據(jù)具體的 key 去 obj 里找 obj[key],來進(jìn)行攔截處理的,所以就有需要滿足一個(gè)前置條件,一開始就得知道 key 是啥,所以就需要遍歷每一個(gè) key,并定義 gettersetter,這也是為什么后面添加的屬性沒有響應(yīng)式的原因

而 Vue3 中則是這樣的

// ref 源碼      `packages/reactivity/ref.ts -142行`
// reactive 源碼 `packages/reactivity/reactive.ts -173行`
new Proxy(target,{  // target 為組件的 data 返回的對(duì)象
  get(target, key){},
  set(target, key, value){}
})

同樣由參數(shù)就可以看出,開始創(chuàng)建響應(yīng)式的時(shí)候,根本不需要知道這個(gè)對(duì)象里有哪些字段,因?yàn)椴挥脗骶唧w的 key,這樣就算是后面新增的,自然也能夠攔截得到

也就是說不會(huì)上來就遞歸遍歷把所有用到?jīng)]用到的都設(shè)置響應(yīng)式,從而加快了首次渲染

數(shù)組

在 Vue2 中

  • 一個(gè)是因?yàn)?Object.defineProperty 這個(gè) api 無(wú)法監(jiān)聽到數(shù)組長(zhǎng)度的變化
  • 二是因?yàn)閿?shù)組長(zhǎng)度可能很長(zhǎng),比如 lenth 是大幾千,上萬(wàn)的,所以尤大考慮到性能消耗與用戶體驗(yàn),設(shè)計(jì)的就是 Vue 本身就不能監(jiān)聽直接通過下標(biāo)修改數(shù)組元素的操作

延伸一個(gè)問題,為什么無(wú)法監(jiān)聽到數(shù)組長(zhǎng)度的變化呢?先看圖

如圖就是 configurable 為 true 時(shí)對(duì)應(yīng)的值才能被改變,也可以理解成才能被監(jiān)聽,而 length 本身是不可以被監(jiān)聽的,所以數(shù)組長(zhǎng)度改變時(shí)也監(jiān)聽不到

如果強(qiáng)行把它的 configurable 修改為 true 則會(huì)報(bào)錯(cuò),因?yàn)楦鞔鬄g覽器廠商和JS引擎規(guī)定就不允許修改 length 的 configurable,規(guī)定就是這樣,所以在源碼里才會(huì)有這樣的代碼

// `src/core/observer/index.js - 144行`
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
    return
}

所以為了更好的操作數(shù)組并觸發(fā)響應(yīng)式,就重寫了會(huì)改變?cè)瓟?shù)組的7個(gè)方法,再通過 ob.dep.notify() 手動(dòng)派發(fā)更新,源碼地址:src/core/observer/array.js

在 Vue3 中使用 Proxy,Proxy 就是代理的意思,回顧一下語(yǔ)法

new Proxy(target,{
  get(target, key){},
  set(target, key, value){}
})

根據(jù) MDN 中對(duì) Proxy 的描述 是這樣的

  • target: 被 Proxy 代理虛擬化的對(duì)象。它常被作為代理的存儲(chǔ)后端。根據(jù)目標(biāo)驗(yàn)證關(guān)于對(duì)象不可擴(kuò)展性或不可配置屬性的不變量(保持不變的語(yǔ)義)

注意了:數(shù)組的 length 就是不可配置的屬性,所以 Proxy 天生就能監(jiān)聽數(shù)組長(zhǎng)度變化

依賴收集的區(qū)別

Vue2 中是通過 ObserverDepWatcher 這三個(gè)類來實(shí)現(xiàn)依賴收集,詳細(xì)流程可以看我另一篇文章 深入淺出 Vue 響應(yīng)式原理源碼剖析

Vue3 中是通過 track 收集依賴,通過 trigger 觸發(fā)更新,本質(zhì)上就是用 WeakMap,Map,Set 來實(shí)現(xiàn),具體可以看下面源碼的實(shí)現(xiàn)過程

缺點(diǎn)區(qū)別

上面也有提到 Vue2 中 defineProperty 監(jiān)聽不到新增對(duì)象屬性/數(shù)組內(nèi)部變化,而且屬性值是對(duì)象的話會(huì)多次調(diào)用 observe() 遞歸遍歷,還有就是會(huì)對(duì)所有數(shù)據(jù)屬性都設(shè)置監(jiān)聽,包括沒有用到的屬性,性能上自然就沒那么好

在 Vue3 中主要就是大量使用 Es6+ 新特性,在老版本瀏覽器上兼容就沒那么好

Vue3 響應(yīng)式源碼解析

先看一下在 Vue3 中定義的幾個(gè)用來標(biāo)記目標(biāo)對(duì)象 target 的類型的flag,下面先是枚舉的屬性

源碼地址:packages/reactivity/reactive.ts -16行

export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  RAW = '__v_raw'
}
export interface Target {
  [ReactiveFlags.SKIP]?: boolean // 不做響應(yīng)式處理的數(shù)據(jù)
  [ReactiveFlags.IS_REACTIVE]?: boolean // target 是否是響應(yīng)式
  [ReactiveFlags.IS_READONLY]?: boolean // target 是否是只讀的
  [ReactiveFlags.RAW]?: any // 表示 proxy 對(duì)應(yīng)的源數(shù)據(jù),target 已經(jīng)是 proxy 對(duì)象時(shí)會(huì)有該屬性
}

然后開始一一解析

reactive()

源碼地址:packages/reactivity/src/reactive.ts -87行

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // 如果 target 是只讀類型的對(duì)象就直接返回
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    target, // 需要?jiǎng)?chuàng)建響應(yīng)式的目標(biāo)對(duì)象 data
    false, // 不是只讀類型
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap // const reactiveMap = new WeakMap<Target, any>()
  )
}

這里代碼很簡(jiǎn)單,主要就是調(diào)用 createReactiveObject()

createReactiveObject()

源碼地址:packages/reactivity/src/reactive.ts -173行

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
  }
  // 已經(jīng)是響應(yīng)式的就直接返回
  if ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
    return target
  }
  // 如果已經(jīng)存在 map 中了,就直接返回
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // 不做響應(yīng)式的,直接返回
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 把 target 轉(zhuǎn)為 proxy
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // 添加到 map 里
  proxyMap.set(target, proxy)
  return proxy
}

大概了解了這個(gè)方法里要做的事,接下來我們還要先明白傳入的幾個(gè)參數(shù)是什么

參數(shù)配置定義是這樣的

const get = /*#__PURE__*/ createGetter()
const set = /*#__PURE__*/ createSetter()
export const mutableHandlers: ProxyHandler<object> = {
  get, // 獲取屬性
  set, // 修改屬性
  deleteProperty, // 刪除屬性
  has, // 是否擁有某個(gè)屬性
  ownKeys // 收集 key,包括 symbol 類型或者不可枚舉的 key
}

這里 get、has、ownKeys 會(huì)觸發(fā)依賴收集 track()
set、deleteProperty 會(huì)觸發(fā)更新 trigger()

其中有兩個(gè)重要的方法就是 get 和 set 對(duì)應(yīng)的 createGetter 和 createSetter

createGetter()

源碼地址:packages/reactivity/src/baseHandlers.ts -80行

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 訪問對(duì)應(yīng)標(biāo)記位
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
      // receiver 指向調(diào)用者,這里判斷是為了保證觸發(fā)攔截 handle 的是 proxy 本身而不是 proxy 的繼承者
      // 觸發(fā)攔的兩種方式:一是訪問 proxy 對(duì)象本身的屬性,二是訪問對(duì)象原型鏈上有 proxy 對(duì)象的對(duì)象的屬性,因?yàn)椴樵儠?huì)沿著原型鏈向下找
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow ? shallowReadonlyMap : readonlyMap
          : shallow ? shallowReactiveMap : reactiveMap
        ).get(target)
    ) {
      // 返回 target 本身,也就是響應(yīng)式對(duì)象的原始值
      return target
    }
    // 是否是數(shù)組
    const targetIsArray = isArray(target)
    // 不是只讀類型 && 是數(shù)組 && 觸發(fā)的是 arrayInstrumentations 工具集里的方法
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      // 通過 proxy 調(diào)用,arrayInstrumentations[key]的this一定指向 proxy
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    // proxy 預(yù)返回值
    const res = Reflect.get(target, key, receiver)
    // key 是 symbol 或訪問的是__proto__屬性不做依賴收集和遞歸響應(yīng)式處理,直接返回結(jié)果
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }
    // 不是只讀類型的 target 就收集依賴。因?yàn)橹蛔x類型不會(huì)變化,無(wú)法觸發(fā) setter,也就會(huì)觸發(fā)更新
    if (!isReadonly) {
      // 收集依賴,存儲(chǔ)到對(duì)應(yīng)的全局倉(cāng)庫(kù)中
      track(target, TrackOpTypes.GET, key)
    }
    // 淺比較,不做遞歸轉(zhuǎn)化,就是說對(duì)象有屬性值還是對(duì)象的話不遞歸調(diào)用 reactive()
    if (shallow) {
      return res
    }
    // 訪問的屬性已經(jīng)是 ref 對(duì)象
    if (isRef(res)) {
      // 返回 ref.value,數(shù)組除外
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }
    // 由于 proxy 只能代理一層,如果子元素是對(duì)象,需要遞歸繼續(xù)代理
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

track() 依賴收集放到后面,和派發(fā)更新一起

createSetter()

源碼地址:packages/reactivity/src/baseHandlers.ts -80行

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) {
      // 拿新值和老值的原始值,因?yàn)樾聜魅氲闹悼赡苁琼憫?yīng)式數(shù)據(jù),如果直接和 target 上原始值比較是沒有意義的
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      // 不是數(shù)組 && 老值是 ref && 新值不是 ref,更新 ref.value 為新值
      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)
    // 賦值,相當(dāng)于 target[key] = value
    const result = Reflect.set(target, key, value, receiver)
    // receiver 是 proxy 實(shí)例才派發(fā)更新,防止通過原型鏈觸發(fā)攔截器觸發(fā)更新
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        // 如果  target 沒有 key,表示新增
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        // 如果新舊值不相等
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

trigger() 派發(fā)更新放到后面

有個(gè)疑問,為什么用 Reflect.get() 和 Reflect.set(),而不是直接用 target[key]?

根據(jù) MDN 介紹 set() 要返回一個(gè)布爾值,比如 Reflect.set() 會(huì)返回一個(gè)是否修改成功的布爾值,直接賦值 target[key] = newValue,而不返回 true 就會(huì)報(bào)錯(cuò)。而且不管 Proxy 怎么修改默認(rèn)行為,都可以通過 Reflect 獲取默認(rèn)行為。get() 同理

接著是依賴收集和派發(fā)更新相關(guān)的核心內(nèi)容,相關(guān)代碼全部在 effect.ts 文件中,該文件主要是處理一些副作用,主要內(nèi)容如下:

  • 創(chuàng)建 effect 入口函數(shù)
  • track 依賴收集
  • trigger 派發(fā)更新
  • cleanupEffect 清除 effect
  • stop 停止 effect
  • trackStack 收集棧的暫停(pauseTracking)、恢復(fù)(enableTracking)和重置(resetTracking)

我們先從入口函數(shù)看起

effect()

源碼地址:packages/reactivity/src/effect.ts -145行

這里主要就是暴露一個(gè)創(chuàng)建 effect 的方法

export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  // 如果已經(jīng)是 effect 函數(shù),就直接拿原來的
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }
  // 創(chuàng)建 effect
  const _effect = new ReactiveEffect(fn)
  if (options) {
    extend(_effect, options)
    if (options.scope) recordEffectScope(_effect, options.scope)
  }
  // 如果 lazy 不為真就直接執(zhí)行一次 effect。計(jì)算屬性的 lazy 為 true
  if (!options || !options.lazy) {
    _effect.run()
  }
  // 返回
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

可以看出主要的就是在 effect 里使用 new ReactiveEffect 創(chuàng)建了一個(gè) _effect 實(shí)例,并且函數(shù)最后返回的 runner 方法就是指向 ReactiveEffect 里的 run 方法

由此可見在執(zhí)行副作用函數(shù) effect 方法時(shí),實(shí)際上執(zhí)行的就是這個(gè) run 方法

所以我們就需要知道知道這個(gè) ReactiveEffect 和它返回的 run 方法,里面都干了些什么

我們繼續(xù)看

ReactiveEffect

源碼地址:packages/reactivity/src/effect.ts -53行

這里主要做的就是在依賴收集前用棧數(shù)據(jù)結(jié)構(gòu) effectStrack 來做 effect 的執(zhí)行調(diào)試,保證當(dāng)前 effect 的優(yōu)先級(jí)最高,并及時(shí)清除己收集依賴的內(nèi)存

需要注意的是標(biāo)記完成后就會(huì)執(zhí)行 fn() 函數(shù),這個(gè) fn 函數(shù)就是副作用函數(shù)封閉的函數(shù),如果是在組件渲染,就是 fn 就是組件渲染函數(shù),執(zhí)行的時(shí)候就會(huì)就會(huì)訪問數(shù)據(jù),就會(huì)觸發(fā) target[key]getter,然后觸發(fā) track 進(jìn)行依賴收集,這也就是 Vue3 的依賴收集過程

// 臨時(shí)存儲(chǔ)響應(yīng)式函數(shù)
const effectStack: ReactiveEffect[] = []
// 依賴收集棧
const trackStack: boolean[] = []
// 最大嵌套深度
const maxMarkerBits = 30
export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []
  computed?: boolean
  allowRecurse?: boolean
  onStop?: () => void
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void
  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope | null
  ) {
    // effectScope 相關(guān)處理,在另一個(gè)文件,這里不過多展開
    recordEffectScope(this, scope)
  }
  run() {
    if (!this.active) {
      return this.fn()
    }
    // 如果棧中沒有當(dāng)前的 effect
    if (!effectStack.includes(this)) {
      try {
        // activeEffect 表示當(dāng)前依賴收集系統(tǒng)正在處理的 effect
        // 先把當(dāng)前 effect 設(shè)置為全局全局激活的 effect,在 getter 中會(huì)收集 activeEffect 持有的 effect
        // 然后入棧
        effectStack.push((activeEffect = this))
        // 恢復(fù)依賴收集,因?yàn)樵趕etup 函數(shù)自行期間,會(huì)暫停依賴收集
        enableTracking()
        // 記錄遞歸深度位數(shù)
        trackOpBit = 1 << ++effectTrackDepth
        // 如果 effect 嵌套層數(shù)沒有超過 30 層,一般超不了
        if (effectTrackDepth <= maxMarkerBits) {
          // 給依賴打標(biāo)記,就是遍歷 _effect 實(shí)例中的 deps 屬性,給每個(gè) dep 的 w 屬性標(biāo)記為 trackOpBit 的值
          initDepMarkers(this)
        } else {
          // 超過就 清除當(dāng)前 effect 相關(guān)依賴 通常情況下不會(huì)
          cleanupEffect(this)
        }
        // 在執(zhí)行 effect 函數(shù),比如訪問 target[key],會(huì)觸發(fā) getter
        return this.fn()
      } finally {
        if (effectTrackDepth <= maxMarkerBits) {
          // 完成依賴標(biāo)記
          finalizeDepMarkers(this)
        }
        // 恢復(fù)到上一級(jí)
        trackOpBit = 1 << --effectTrackDepth
        // 重置依賴收集狀態(tài)
        resetTracking()
        // 出棧
        effectStack.pop()
        // 獲取棧長(zhǎng)度
        const n = effectStack.length
        // 將當(dāng)前 activeEffect 指向棧最后一個(gè) effect
        activeEffect = n > 0 ? effectStack[n - 1] : undefined
      }
    }
  }
  stop() {
    if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

track()

源碼地址:packages/reactivity/src/effect.ts -188行

track 就是依賴收集器,負(fù)責(zé)把依賴收集起來統(tǒng)一放到一個(gè)依賴管理中心

// targetMap 為依賴管理中心,用于存儲(chǔ)響應(yīng)式函數(shù)、目標(biāo)對(duì)象、鍵之間的映射關(guān)系
// 相當(dāng)于這樣
// targetMap(weakmap)={
//    target1(map):{
//      key1(dep):[effect1,effect2]
//      key2(dep):[effect1,effect2]
//    }
// }
// 給每個(gè) target 創(chuàng)建一個(gè) map,每個(gè) key 對(duì)應(yīng)著一個(gè) dep
// 用 dep 來收集依賴函數(shù),監(jiān)聽 key 值變化,觸發(fā) dep 中的依賴函數(shù)
const targetMap = new WeakMap<any, KeyToDepMap>()
export function isTracking() {
  return shouldTrack && activeEffect !== undefined
}
export function track(target: object, type: TrackOpTypes, key: unknown) {
  // 如果當(dāng)前沒有激活 effect,就不用收集
  if (!isTracking()) {
    return
  }
  // 從依賴管理中心里獲取 target
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    // 如果沒有就創(chuàng)建一個(gè)
    targetMap.set(target, (depsMap = new Map()))
  }
  // 獲取 key 對(duì)應(yīng)的 dep 集合
  let dep = depsMap.get(key)
  if (!dep) {
    // 沒有就創(chuàng)建
    depsMap.set(key, (dep = createDep()))
  }
  // 開發(fā)環(huán)境和非開發(fā)環(huán)境
  const eventInfo = __DEV__
    ? { effect: activeEffect, target, type, key }
    : undefined
  trackEffects(dep, eventInfo)
}

trackEffects()

源碼地址:packages/reactivity/src/effect.ts -212行

這里把當(dāng)前激活的 effect 收集進(jìn)對(duì)應(yīng)的 effect 集合,也就是 dep

這里了解一下兩個(gè)標(biāo)識(shí)符

dep.n:n 是 newTracked 的縮寫,表示是否是最新收集的(是否當(dāng)前層)
dep.w:w 是 wasTracked 的縮寫,表示是否已經(jīng)被收集,避免重復(fù)收集

export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  // 如果 effect 嵌套層數(shù)沒有超過 30 層,上面說過了
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      // 標(biāo)記新依賴
      dep.n |= trackOpBit
      // 已經(jīng)被收集的依賴不需要重復(fù)收集
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // 超過了 就切換清除依賴模式
    shouldTrack = !dep.has(activeEffect!)
  }
  // 如果可以收集
  if (shouldTrack) {
    // 收集當(dāng)前激活的 effect 作為依賴
    dep.add(activeEffect!)
    // 當(dāng)前激活的 effect 收集 dep 集合
    activeEffect!.deps.push(dep)
    // 開發(fā)環(huán)境下觸發(fā) onTrack 事件
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack(
        Object.assign(
          {
            effect: activeEffect!
          },
          debuggerEventExtraInfo
        )
      )
    }
  }
}

trigger()

源碼地址:packages/reactivity/src/effect.ts -243行

trigger 是 track 收集的依賴對(duì)應(yīng)的觸發(fā)器,也就是負(fù)責(zé)根據(jù)映射關(guān)系,獲取響應(yīng)式函數(shù),再派發(fā)通知 triggerEffects 進(jìn)行更新

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  // 從依賴管理中心中獲取依賴
  const depsMap = targetMap.get(target)
  // 沒有被收集過的依賴,直接返回
  if (!depsMap) {
    return
  }

  let deps: (Dep | undefined)[] = []
  // 觸發(fā)trigger 的時(shí)候傳進(jìn)來的類型是清除類型
  if (type === TriggerOpTypes.CLEAR) {
    // 往隊(duì)列中添加關(guān)聯(lián)的所有依賴,準(zhǔn)備清除
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    // 如果是數(shù)組類型的,并且是數(shù)組的 length 改變時(shí)
    depsMap.forEach((dep, key) => {
      // 如果數(shù)組長(zhǎng)度變短時(shí),需要做已刪除數(shù)組元素的 effects 和 trigger
      // 也就是索引號(hào) >= 數(shù)組最新的length的元素們對(duì)應(yīng)的 effects,要將它們添加進(jìn)隊(duì)列準(zhǔn)備清除
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // 如果 key 不是 undefined,就添加對(duì)應(yīng)依賴到隊(duì)列,比如新增、修改、刪除
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }
    // 新增、修改、刪除分別處理
    switch (type) {
      case TriggerOpTypes.ADD: // 新增
        ...
        break
      case TriggerOpTypes.DELETE: // 刪除
        ...
        break
      case TriggerOpTypes.SET: // 修改
        ...
        break
    }
  }
  // 到這里就拿到了 targetMap[target][key],并存到 deps 里
  // 接著是要將對(duì)應(yīng)的 effect 取出,調(diào)用 triggerEffects 執(zhí)行

  // 判斷開發(fā)環(huán)境,傳入eventInfo
  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined

  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

triggerEffects()

源碼地址:packages/reactivity/src/effect.ts -330行

執(zhí)行 effect 函數(shù),也就是『派發(fā)更新』中的更新了

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // 遍歷 effect 的集合函數(shù)
  for (const effect of isArray(dep) ? dep : [...dep]) {
    /** 
      這里判斷 effect !== activeEffect的原因是:不能和當(dāng)前effect 相同
      比如:count.value++,如果這是個(gè)effect,會(huì)觸發(fā)getter,track收集了當(dāng)前激活的 effect,
      然后count.value = count.value+1 會(huì)觸發(fā)setter,執(zhí)行trigger,
      就會(huì)陷入一個(gè)死循環(huán),所以要過濾當(dāng)前的 effect
    */
    if (effect !== activeEffect || effect.allowRecurse) {
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
      }
      // 如果 scheduler 就執(zhí)行,計(jì)算屬性有 scheduler
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        // 執(zhí)行 effect 函數(shù)
        effect.run()
      }
    }
  }
}

創(chuàng)建 ref

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

這里開始主要就是處理 ref 相關(guān)的了,先看一下幾個(gè)相關(guān)函數(shù),后面會(huì)用的

// 判斷是不是 ref
export function isRef(r: any): r is Ref {
  return Boolean(r && r.__v_isRef === true)
}
// 創(chuàng)建 ref
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) { // 如果已經(jīng)是 ref 就直接返回
    return rawValue
  }
  // 調(diào)用 RefImpl 創(chuàng)建并返回 ref
  return new RefImpl(rawValue, shallow)
}
// 創(chuàng)建一個(gè)淺層 ref
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}
// 卸載一個(gè) ref
export function unref<T>(ref: T | Ref<T>): T {
  return isRef(ref) ? (ref.value as any) : ref
}

RefImpl

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

從上面我們知道 ref 對(duì)象是通過 new RefImpl() 創(chuàng)建的,RefImpl 類的實(shí)現(xiàn)比較簡(jiǎn)單,這里就不多廢話了,請(qǐng)看注釋

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

  public dep?: Dep = undefined
  // 每一個(gè) ref 實(shí)例下都有一個(gè)__v_isRef 的只讀屬性,標(biāo)識(shí)它是一個(gè) ref
  public readonly __v_isRef = true

  constructor(value: T, public readonly _shallow: boolean) {
    // 判斷是不是淺比較,如果不是就拿老值
    this._rawValue = _shallow ? value : toRaw(value)
    // 判斷是不是淺比較,如果不是就調(diào)convert,判斷如果是對(duì)象就調(diào)用 reactive()
    this._value = _shallow ? value : convert(value)
  }
  // ref.value 這樣取值
  get value() {
    // 進(jìn)行依賴收集
    trackRefValue(this)
    return this._value
  }
  set value(newVal) {
    // 如果是淺比較,就取新值,不是就取老值
    newVal = this._shallow ? newVal : toRaw(newVal)
    // 比較新舊值
    if (hasChanged(newVal, this._rawValue)) {
      // 值已更換,重新賦值
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      // 派發(fā)更新
      triggerRefValue(this, newVal)
    }
  }
}

trackRefValue()

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

這里主要做一些 ref 依賴收集之前的工作,主要就是判斷是否激活了 effect,有沒有收集過依賴的 effect,沒有就創(chuàng)建一個(gè) dep,準(zhǔn)備收集,然后調(diào)用本文上面的 trackEffects 進(jìn)行依賴收集

export function trackRefValue(ref: RefBase<any>) {
  // 如果激活了 effect,就收集
  if (isTracking()) {
    ref = toRaw(ref)
    // 如果該屬性沒有沒有收集過依賴函數(shù),就創(chuàng)建一個(gè) dep,用來存放依賴 effect
    if (!ref.dep) {
      ref.dep = createDep()
    }
    // 開發(fā)環(huán)境
    if (__DEV__) {
      trackEffects(ref.dep, {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      // 調(diào)用本文上面的 trackEffects 收集依賴
      trackEffects(ref.dep)
    }
  }
}

triggerRefValue()

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

這里進(jìn)行 ref 派發(fā)更新,源碼比較簡(jiǎn)單,沒啥說的,就是區(qū)分一下開發(fā)環(huán)境,然后執(zhí)行本文上面的 triggerEffects 執(zhí)行對(duì)應(yīng)在的 effect 函數(shù)進(jìn)行更新

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)
    }
  }
}

到這里,Vue3 的響應(yīng)式對(duì)象的源碼就基本上剖析結(jié)束了

往期精彩

結(jié)語(yǔ)

如果本文對(duì)你有一丁點(diǎn)幫助,點(diǎn)個(gè)贊支持一下吧,感謝感謝

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容