理解組件通訊之:provide / inject

理解組件通訊之:provide / inject

在前端的Vue項目中,若是須要跨組件通訊,好比父組件A,子組件B,孫子組件C,A給C傳遞數據,又不想使用Vuex,Bus等方式,引入第三方庫,想更簡單的實現組件跨級通訊,那麼Vue的原生API,provide / inject 是個不錯的選擇。html

什麼是 provide / inject

provide / inject 是vue 2.2.0 版本增長的方法,這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。前端

文檔地址:cn.vuejs.org/v2/api/#pro…vue

vue官方文檔說,主要爲高階插件/組件庫提供用例,並不推薦直接用於應用程序代碼中。react

雖然官方這麼建議,可是若是學會了這種方法,結合須要的業務,仍是能發揮出很好的效果的。vuex

使用方式

  • provide:Object | () => Objectapi

  • inject:Array<string> | { [key: string]: string | Symbol | Object }數組

  • provide :能夠是一個對象或者是一個返回對象的函數bash

  • inject :能夠是一個字符串類型的數組或者是一個對象app

// A.vue
export default {
  // provide 是一個對象
  provide: {
    name: 'dale'
  }
}

// B.vue
export default {
  // inject 爲一個字符串數組
  inject: ['name'],
  mounted () {
    console.log(this.name);  // dale
  }
}

複製代碼

可是須要注意的是provideinject 綁定並非可響應的,也就是說父組件改變了值,默認狀況下後輩組件是不會響應式變化的,可是這個能夠用其餘方法實現響應式變化。ide

好比給 provide 裏面傳入響應式對象:

// A.vue
export default {
    data(){
      return {
        number:0
      }
    },
  // 
  // provide 是一個返回兌現的函數
  provide() {
    let observeObject = this.$data
    return {
      name: observeObject
    }
  },
  methods:{
    // 改變number ,B.vue 會接收到更新,由於$data是一個響應式對象
  }
  
}

複製代碼

採用 Vue.observable 構建出一個響應式對象,一樣能夠實現響應式更新數據。

// A.vue
const observeObject = Vue.observable({ number: 0 })
export default {

  // provide 是一個返回兌現的函數
  provide() {
    return {
      name: observeObject
    }
  },
  methods:{
    // observeObject.number 變化,B.vue 會接收到更新,由於observeObject是一個響應式對象
    // data的返回對象也是通過此api處理的
  }
  
}

複製代碼

響應式原理: cn.vuejs.org/v2/guide/re…

擴展延伸

provideinject 的其餘用法,高級技巧嘗試。

app.vue 文件中把this 做爲provide的值,能夠把實例對外提供。全部子組件均可以直接經過 this.app.xxx 來訪問 app.vue 的 data、computed、methods 等內容。

app.vue 是整個項目第一個被渲染的組件,並且只會渲染一次(即便切換路由,app.vue 也不會被再次渲染),利用這個特性,比較適合作一次性全局的狀態數據管理,好比全局的登陸信息等, 而不須要使用vuex等第三方狀態管理工具。

app.vue

export default {
    provide () {
      return {
        app: this
      }
    }
    // ...
 }
複製代碼
// 子組件
    <template>
      <div>
        {{ app.userInfo }}
      </div>
    </template>
    <script>
      export default {
        inject: ['app']
      }
    </script>

複製代碼

源碼分析

vue 2.x 源碼

  • inject/provide 本質仍是經過$parent向上查找祖先節點數據
// src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    ...
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

複製代碼
// inject裏的key,經過$parent向上找到provide值,再進行響應式監聽
export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    Object.keys(result).forEach(key => {
        defineReactive(vm, key, result[key]) // 響應式數據
    })
  }
}

export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    const result = Object.create(null)
    const keys = Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      const provideKey = inject[key].from
      let source = vm
      // 循環向上,直到拿到祖先節點中的provide值
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey] // provide是在initProvide中設置的
          break
        }
        source = source.$parent // 關鍵代碼
      }
    }
    return result
  }
}

複製代碼
// 單純把provide值,賦值給vm._provided。initInject中會使用到
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

複製代碼
相關文章
相關標籤/搜索