在 Vue 2.2.0 新增了provide/inject 组合使用的 API。这个 API 的作用是可以让一个祖先组件向其所有子孙组件传递数据。这很像父子组件使用 props 传递数据,但是完全不一样,其实可以这样理解,props 更像是通过构造函数参数来创建对象,而 provide/inject 是在构建的对象中注入所依赖的数据,使用的是依赖注入的思想。
使用说明
provide 在祖先组件使用,inject 在子孙组件使用。
provide 可以是一个对象,或是一个返回对象的函数。Object | () => Object
子孙组件可通过这个对象的 key,获取到要依赖的数据。
inject 可以是一个字符串数组,数组中字符串即 provide 中对象的key;也可以是一个对象,这个对象的 key 是子组件的 property,value 可以是 provide 对象中的 key,也可以是一个对象(2.5.0 新增),对象包含 from、default两个属性,from 对应的值 为 provide 对象中的 key,default 是未找到相应 key 的情况下使用的值。
通过下面例子,来看一下各种写法:
const Provider = {
provide: {
foo: 'bar' // 提供向子孙组件可注入的数据 'bar'
}
}
const Child1 = {
inject: ['foo'], // 字符串数组
create() { console.log(this.foo) } // 'bar' 获取到注入的 'bar'
}
const Child2 = {
inject: {
ffo: 'foo' // 对象形式
},
create() { console.log(this.ffo) } // 'bar' 获取到注入的 'bar'
}
const Child2 = {
inject: {
ffo: 'foo',
fff: { // 如果 key 和 对象里 from 的值一样,对象中 from 可省略不写
from: 'fff', // 可省略
default: 'bbr' // 如果没找到 'fff' 对应的数据,就使用 'bbr'
}
},
create() {
console.log(this.ffo); // 'bar'
console.log(this.fff); // 'bbr' 由于在 Provider 中未提供 'fff' 对应数据
}
}
整体来看,这个 API 使用并不复杂。另外 provide/inject 的特性并没有设计为响应式,也许是设计者并不想让其承载过多额外的功能。
源码学习
从官方文档例子,就可以了解到,provide/inject 应该都是在实例化时,进行处理。那么这部分功能可以在 Vue 的 src/core/instance/inject.js 中找到。
initProvide
function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
初始化 provide 很简单,即把 provide 对象放到 vm 的 _provided 上。
initInjections
function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
...
Object.keys(result).forEach(key => {
...
defineReactive(vm, key, result[key])
...
})
...
}
}
这也很好理解,就是把要注入的数据找到,然后放到 vm 的响应式数据上。所以这部分核心的代码就是 resolveInject,即如果找到这些要注入的数据。
resolveInject
function resolveInject (inject: any, vm: Component): ?Object {
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]
...
const provideKey = inject[key].from
let source = vm
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
...
}
return result
}
}
我们不考虑其中的细节,发现使用 while 循环,通过 $parent 逐级往上找,找到 vm 上的 provide,如果找到对应数据就结束。
总结
provide/reject 提供的特性,可以简单方便地实现祖先组件向子孙组件注入依赖数据。这种特性常用于开发高阶插件/组件库,因为这类代码组件层级较深又有较为紧密的联系,所以会将整个组件库看成一个整体,而没必要特意将这些子孙组件设计为可脱离此环境的独立组件。