Provide/Inject
- Provide: 提供一个值,可以被后代组件注入。
- Inject:注入一个由祖先组件或整个应用 (通过
app.provide()) 提供的值
在日常使用时,我们通常在祖先组件使用provide提供具有跨层级传递能力的属性,然后在子孙组件inject接收,然而这样似乎很难看出其底层究竟是如何实现的。
关于实现
现在假设我们已经实现了获取组件实例的方法,并在组件实例currentInstance绑定了一系列属性(包括el,props,vnode等等), 如果我们只是实现父与子组件的provide,inject能力,我们可以为每一个组件实例绑定一个providers对象,作为保存provide的对象,然后再在当前组件实例上绑定parentVNode用于获取父组件的providers对象,那么每次inject时,我们就可以根据inject的key在父组件providers内进行查找。
那么如果是跨层级的呢? 如果是跨层级的传递,我们可能会想维护一个全局的providers,但这必然面临key的冲突,所以是不可行的,所以我们可以看以下一种情况:定义三个组件,祖先组件、父组件、子组件,
// 祖先组件
const foo = ref(1);
provide('foo', foo);
// 父组件
const foo = ref(2);
provide('foo', foo);
// 子组件
const bar = inject('foo')
这种情况下, 子组件bar.value实际会是2,而不是1,即会接收父组件provide的值,而不是层级更高的祖先组件provide的值,这种查找方式就类似于查找对象上的属性,即原型链继承。实际上,provide\inject就是基于原型链继承查找的。
在组件实例上,我们仍会维护自己的一个providers对象,作为保存provide的对象, 其次在每次inject,进行查找时,我们会在父组件的providers进行查找,关键的一步在于:在创建providers时,我们会以父组件的providers对象为原型进行创建,达到继承效果,这里我们直接使用Object.create方法,即currentInstance.providers.prototype = parentInstance.providers, 下面贴出关键步骤
export function provide(key, value) {
// 存
const currentInstance: any = getCurrentInstance();
if (currentInstance) {
let { provides } = currentInstance;
const parentProvides = currentInstance.parent.provides;
// 只在init阶段进行,因为当多次调用`provide`时,每次都调用会覆盖前面的`provides`
// 如何判断时init状态? 当我们为instance对象初始化时,provides被赋值为parent.provides,根据这一点判断
if (provides === parentProvides) {
provides = currentInstance.provides = Object.create(parentProvides);
}
provides[key] = value;
}
}
export function inject(key, defaultVal) {
const currentInstance: any = getCurrentInstance();
if (currentInstance) {
const parentProvides = currentInstance.parent.provides;
if (key in parentProvides) {
return parentProvides[key];
} else if (defaultVal) {
if (typeof defaultVal === "function") return defaultVal();
else return defaultVal;
}
}
}