Vue 2源碼閱讀 Provide Inject 依賴註入詳解

Provide/Inject 初始化

1. initInjections 依賴初始化

該步驟其實發生在 initState 之前,但是由於 provide/inject 一般是配合使用,所以這裡調整瞭一下順序。

該函數的定義與過程都比較簡單:

export function initInjections(vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      if (__DEV__) {
        defineReactive(vm, key, result[key], () => warn(''))
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}
export function resolveInject(inject: any, vm: Component): Record<string, any> | undefined | null {
  if (inject) {
    const result = Object.create(null)
    const keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      if (key === '__ob__') continue
      const provideKey = inject[key].from
      if (provideKey in vm._provided) {
        result[key] = vm._provided[provideKey]
      } else if ('default' in inject[key]) {
        const provideDefault = inject[key].default
        result[key] = isFunction(provideDefault) ? provideDefault.call(vm) : provideDefault
      } else if (__DEV__) {
        warn('')
      }
    }
    return result
  }
}
  • 在 initInjections 函數中,隻是遍歷瞭 options.inject 配置的依賴數據,並 關閉 瞭依賴數據的 響應式依賴收集,最後通過 defineReactive 將對應的數據掛載到實例 vm 上,以便後面能直接訪問。

這就是官方提示的 為什麼 provide/inject 的數據不是響應式的瞭。

  • 而 resolveInject 函數就是用來對組件的 inject 依賴數據進行處理,並返回一個沒有多餘原型鏈的對象。

在官方文檔中,inject 接收一個字符串數組或者一個 key 為 string 的對象,而作為對象時則 必須 有 from 字段來表示依賴數據的獲取指向,另外也接收一個 default 屬性作為降級時使用的默認值。

但是,在 mergeOptions 之後,會將 options.inject 轉為標準對象格式。

並且這裡並沒有對註入數據 provide[key] 進行處理,而是直接賦值;所以才有:如果你傳入瞭一個可監聽的對象,那麼其對象的 property 還是可響應的。

resolveInject() 函數就是解析標準格式 inject 配置,並將上層組件的 provide 的值或者 default 默認值綁定到函數返回對象中;如果這兩個都沒有,則會提示錯誤信息 “injection xx not found”

2. initProvide 註入數據初始化

初始化註入數據的過程也很簡單,整個過程其實與 initInjection 類似。其函數定義如下:

export function initProvide(vm: Component) {
  const provideOption = vm.$options.provide
  if (provideOption) {
    const provided = isFunction(provideOption) ? provideOption.call(vm) : provideOption
    if (!isObject(provided)) {
      return
    }
    const source = resolveProvided(vm)
    
    const keys = hasSymbol ? Reflect.ownKeys(provided) : Object.keys(provided)
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      Object.defineProperty(
        source,
        key,
        Object.getOwnPropertyDescriptor(provided, key)!
      )
    }
  }
}
export function resolveProvided(vm: Component): Record<string, any> {
  const existing = vm._provided
  const parentProvides = vm.$parent && vm.$parent._provided
  if (parentProvides === existing) {
    return (vm._provided = Object.create(parentProvides))
  } else {
    return existing
  }
}

官方文檔中對 provide 配置項的說明是,可以是一個對象或者一個返回對象的函數。

  • 所以這裡首先判斷瞭 options.provide 的類型並獲取到瞭結果,如果結果 不是對象則會直接退出。
  • 然後,則是初始化 provide 的數據。

此時會將當前實例的 provided 數據與父組件實例的 provided 進行比較,如果相同,則返回一個 以父組件實例 provided 數據為原型創建的對象,否則直接返回當前實例的 provided 數據。

因為每一個實例都會進行與父組件實例的註入數據比較,所以才能多層級傳遞

  • 最後,則是遍歷 provided 對象,通過 Object.defineProperty 來處理數據獲取。

總結

整個 provide/inject 的初始化過程都很清晰,隻是通過少數校驗和處理,將 provide 數據一層一層傳遞下去,直到 inject 依賴時讀該改數據的值;

並且因為在初始化時會關閉響應式處理部分,所以 provide/inject 的 直接綁定數據 才不支持響應式;但

又因為 沒有對數據的進行深層次處理,所以,原有的響應式數據才會繼續觸發整個響應式系統的改變。

以上就是Vue 2源碼閱讀 Provide Inject 依賴註入詳解的詳細內容,更多關於Vue Provide Inject 依賴註入的資料請關註WalkonNet其它相關文章!

推薦閱讀: