Vue源码学习之initInjections和initProvide
在进行源码阅读之前先让我们了解一个概念:provide/inject,这个是Vue在2.2.0版本新增的一个属性,按照Vue官网的说法,它的作用是:
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
简而言之就是由祖先组件提供了一个provide,provide里的属性可供所有的后代组件通过inject注入并调用,官网实例如下:
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
initInjections
该方法在初始化data/props之前被调用,主要作用是初始化vue实例的inject,让我们一行一行地看代码吧!
const result = resolveInject(vm.$options.inject, vm)
首先就是一个对inject进行处理的方法resolveInejct,让我们看下这个方法的实现。
const result = Object.create(null)
// 对Symbol进行不同的处理
const keys = hasSymbol ? Reflect.ownKeys(inject).filter(key => {
/* istanbul ignore next */
return Object.getOwnPropertyDescriptor(inject, key).enumerable
}) : Object.keys(inject)
先建立一个存放结果的空对象,由于provide支持Symbols作为key,所以要对symbol和普通的对象进行不同的处理。
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const provideKey = inject[key].from
let source = vm
while (source) {
// 验证父组件链逐层向上查找provide,直到找到为止
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
这段代码主要就是对inject属性中的各个key进行遍历,然后沿着父组件链一直向上查找provide中和inject对应的属性,直到查找到根组件或者找到为止,然后返回结果。
让我们接着回到initInjections方法:
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
可以看到从provide中找到了inject的值之后,接下来就是对result的一些处理,比如在非生产环境会将result里的值定义成相应式的。
initProvide
function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
initProvide基本没有什么内容,就是将$options里的provide赋值到当前实例上