上俩片文章我们讲到了initLifecycle和initEvents函数,本篇是时候讲讲initInjections初始化函数了。该函数就是用来去实例化inject选项。说到inject,那就必然离不开provide,这俩是成对出现的,它们的作用是:允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
provide:是一个对象,或者是一个返回对象的函数。
inject:是一个字符串数组,或者是一个多层对象。
这俩个东西,在组件中用的比较多。总结来说,就是父组件可以使用provide选项给自己的下游子孙组件内注入一些数据,在下游子孙组件中可以使用inject选项来接收这些数据以便为自己所用。provide 和 inject 选项绑定的数据不是响应式的。
我们发现,provide和inject函数是成对出现,但是,在init的初始化中发现,provide和inject之间有一个initstate函数,其实不然,provide选项注入的值作为数据入口,也就是data、props、watch、computed及method,所以inject之前可能会被以上的这些使用到,所以在初始化inject后需要初始化这些数据,然后才能初始化provide。在调用initInjections函数对inject初始化完之后需要先调用initState函数对数据进行初始化,最后再调用initProvide函数对provide进行初始化。
initInjections函数的原理:
// src/core/instance/inject.js export function initInjections (vm: Component) { const result = resolveInject(vm.$options.inject, vm) if (result) { toggleObserving(false) Object.keys(result).forEach(key => { defineReactive(vm, key, result[key]) } toggleObserving(true) } } export let shouldObserve: boolean = true export function toggleObserving (value: boolean) { shouldObserve = value }首先调用resolveInject把inject选项中的数据转化成键值对的形式赋给result,result的结果应该是这样的。
// 父级组件提供 'foo' var Parent = { provide: { foo: 'bar' } } // 子组件注入 'foo' var Child = { inject: ['foo'], } // result result = { 'foo':'bar' }然后遍历result中的每一对键值,调用defineReactive函数将其添加当前实例上。我们发现遍历之前调用了toggleObserving(false)函数,这是在告诉defineReactive函数仅仅是把键值添加到当前实例上而不需要将其转换成响应式。要不然如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
首先创建一个空对象result,用来存储inject 选项中的数据key及其对应的值,作为最后的返回结果。然后获取当前inject 选项中的所有key,然后遍历每一个key,拿到每一个key的from属性记作provideKey,provideKey就是上游父级组件提供的源属性,然后开启一个while循环,从当前组件起,不断的向上游父级组件的_provided属性中(父级组件使用provide选项注入数据时会将注入的数据存入自己的实例的_provided属性中)查找,直到查找到源属性的对应的值,将其存入result中。如果没有找到,那么就看inject 选项中当前的数据key是否设置了默认值,即是否有default属性,如果有的话,则拿到这个默认值,官方文档示例中说了,默认值可以为一个工厂函数,所以当默认值是函数的时候,就去该函数的返回值,否则就取默认值本身。如果没有设置默认值,则抛出异常。最后返回result。
ok,之前说到inject是一个字符串数组,但是没有处理,这是为什么呢?其实在初始化阶段_init函数在合并属性的时候还调用了一个将inject 选项数据规范化的函数normalizeInject。这样做的目的是,不管用户使用了何种写法,统统将其转化成一种便于集中处理的写法。
function normalizeInject (options: Object, vm: ?Component) { const inject = options.inject if (!inject) return const normalized = options.inject = {} if (Array.isArray(inject)) { for (let i = 0; i < inject.length; i++) { normalized[inject[i]] = { from: inject[i] } } } else if (isPlainObject(inject)) { for (const key in inject) { const val = inject[key] normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val } } } else if (process.env.NODE_ENV !== 'production') { warn( `Invalid value for option "inject": expected an Array or an Object, ` + `but got ${toRawType(inject)}.`, vm ) } }这个函数的逻辑不复杂,也是一目了然,就是在处理传入的inject,将其格式化。。。。
未完,待续。。。
