Vue源码解析:Vue生命周期之从生到死(三)

    技术2026-03-28  19

    上俩片文章我们讲到了initLifecycle和initEvents函数,本篇是时候讲讲initInjections初始化函数了。该函数就是用来去实例化inject选项。说到inject,那就必然离不开provide,这俩是成对出现的,它们的作用是:允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

    provide:是一个对象,或者是一个返回对象的函数。

    inject:是一个字符串数组,或者是一个多层对象。

    这俩个东西,在组件中用的比较多。总结来说,就是父组件可以使用provide选项给自己的下游子孙组件内注入一些数据,在下游子孙组件中可以使用inject选项来接收这些数据以便为自己所用。provide 和 inject 选项绑定的数据不是响应式的。

    initInjections是何方神圣?

    我们发现,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函数仅仅是把键值添加到当前实例上而不需要将其转换成响应式。要不然如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

    resolveInject又干了什么?

    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 while (source) { 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 } }

    首先创建一个空对象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,将其格式化。。。。

    未完,待续。。。

    Processed: 0.027, SQL: 9