Provide/Inject与原型链继承

327 阅读2分钟

Provide/Inject

  • Provide: 提供一个值,可以被后代组件注入。
  • Inject:注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值

在日常使用时,我们通常在祖先组件使用provide提供具有跨层级传递能力的属性,然后在子孙组件inject接收,然而这样似乎很难看出其底层究竟是如何实现的。

关于实现

现在假设我们已经实现了获取组件实例的方法,并在组件实例currentInstance绑定了一系列属性(包括el,props,vnode等等), 如果我们只是实现父与子组件的provide,inject能力,我们可以为每一个组件实例绑定一个providers对象,作为保存provide的对象,然后再在当前组件实例上绑定parentVNode用于获取父组件的providers对象,那么每次inject时,我们就可以根据injectkey在父组件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;
    }
  }
}