Vue API 之 — provide/inject

在 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 提供的特性,可以简单方便地实现祖先组件向子孙组件注入依赖数据。这种特性常用于开发高阶插件/组件库,因为这类代码组件层级较深又有较为紧密的联系,所以会将整个组件库看成一个整体,而没必要特意将这些子孙组件设计为可脱离此环境的独立组件。

参考

https://cn.vuejs.org/v2/api/#provide-inject

你可能感兴趣的:(Vue API 之 — provide/inject)