Vue3读源码系列(二):provide/inject原理

70 阅读2分钟

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获取值的顺序就是普通对象的取值顺序,会沿着原型链向上查找,直到找到为止。