在前端的Vue項目中,若是須要跨組件通訊,好比父組件A,子組件B,孫子組件C,A給C傳遞數據,又不想使用Vuex,Bus等方式,引入第三方庫,想更簡單的實現組件跨級通訊,那麼Vue的原生API,provide / inject
是個不錯的選擇。html
provide / inject
是vue 2.2.0 版本增長的方法,這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。前端
文檔地址:cn.vuejs.org/v2/api/#pro…vue
vue官方文檔說,主要爲高階插件/組件庫提供用例,並不推薦直接用於應用程序代碼中。react
雖然官方這麼建議,可是若是學會了這種方法,結合須要的業務,仍是能發揮出很好的效果的。vuex
provide:Object | () => Object
api
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
}
}
複製代碼
可是須要注意的是provide
和 inject
綁定並非可響應的,也就是說父組件改變了值,默認狀況下後輩組件是不會響應式變化的,可是這個能夠用其餘方法實現響應式變化。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…
provide
和 inject
的其餘用法,高級技巧嘗試。
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 源碼
// 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
}
}
複製代碼