provide/inject可用于跨组件数据传递,那么他到底是怎么实现的呢?
provide
先来看provide实现:
// packages/runtime-core/src/apiInject.ts
export function provide<T>(key: InjectionKey<T> | string | number, value: T) {
if (!currentInstance) {
if (__DEV__) {
warn(`provide() can only be used inside setup().`)
}
} else {
let provides = currentInstance.provides
// 默认情况下,实例继承其父级的提供对象,但是当它需要提供自己的值时,
// 它会使用父级提供对象作为原型创建自己的提供对象。
// 这样,在“inject”中,我们可以简单地从直接父级查找注入,并让原型链完成工作。
const parentProvides =
currentInstance.parent && currentInstance.parent.provides
if (parentProvides === provides) {
provides = currentInstance.provides = Object.create(parentProvides)
}
// TS doesn't allow symbol as index type
provides[key as string] = value
}
}
默认情况下,当前组件的provides就是父组件的provides。在组件实例创建的时候是这样给provides属性默认值的:provides: parent ? parent.provides : Object.create(appContext.provides)
有父组件就使用父组件的provides,根组件则使用appContext.provides。
所以如果当前组件要使用provide分享自己的状态,那么就会执行if判断中的代码,当前组件的provides会被赋值为一个新的对象,这个对象的原型是父组件的provides。到这里其实我们已经能看出来provide API是通过对象的原型链来实现跨组件的数据传递。
inject
我们再来看inject API的实现:
export function inject(
key: InjectionKey<any> | string,
defaultValue?: unknown,
treatDefaultAsFactory = false
) {
...
if (instance) {
const provides =
instance.parent == null
// 根组件
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides
// 这里使用in可以实现原型链查找 来判断key是否存在
if (provides && (key as string | symbol) in provides) {
// TS doesn't allow symbol as index type
return provides[key as string]
} else if (arguments.length > 1) {
return treatDefaultAsFactory && isFunction(defaultValue)
? defaultValue.call(instance.proxy)
: defaultValue
}
}
}
inject的实现也很简单,首先拿到父组件(根组件是appContext)的provides,然后使用in操作符判断key是否存在(这里注意in操作符是在整条原型链上进行查找的),如果存在直接取出返回,没有则使用传入的默认值。
这里我们也能看出来inject获取值的顺序就是普通对象的取值顺序,会沿着原型链向上查找,直到找到为止。