深究vue3中provide与inject

2,137 阅读3分钟

在vue中,组件之间的通讯方式有很多种, 比如父组件向子组件传参使用props、子组件与父组件通信使用emit、兄弟组件通信使用eventBus/(provides/inject)。现在就一起来研究一下provide与inject的实现吧.

provide

在开发过程中, 我们经常会在父组件或更上级的组件中使用provide, 然后在子孙组件中使用inject来接受, vue中的provideAPI给我们的开发带来了很大的便利,而且在使用过程中, 父级组件不需要知道哪些子孙组件在调用当前的provide, 而子孙组件也不需要知道当前inject调用的函数来自哪里。 下面让我们一起来看一下vue3源码中关于provideAPI的实现吧。

function provide<T>(key: InjectionKey<T> | string | number, value: T) {
  // 如果定义当前provide的组件不存在 在开发环境下发出警告⚠️
  if (!currentInstance) {
    if (__DEV__) {
      warn(`provide() can only be used inside setup().`)
    }
  } else {
    let provides = currentInstance.provides
    const parentProvides =
      currentInstance.parent && currentInstance.parent.provides
    if (parentProvides === provides) {
      provides = currentInstance.provides = Object.create(parentProvides)
    }
    // TS doesn't allow symbol as index type
    // 相同key值的情况下 父级组件的provide会覆盖根组件的provide
    provides[key as string] = value
  }
}

可以看到,相对与watchAPI来说 provide的代码并不是很长, 让我带大家一起分析一下吧. 从代码中可以看到 provide函数其实只做了一件事情, 就是将当前传入的函数添加到了provides对象中, 在其中需要注意的一点是, 当你在根组件传入一个provide之后, 如果key值相同,在父组件中传入的provide会覆盖根组件中的provide, 这点在开发过程中还是需要注意的. 接下来一起在看一下inject的实现吧.

inject

上面说到provideAPI方法将接受的所有key、value全部放到了一个provides对象中, 那么有些小伙伴应该可以猜到, inject函数其实是对provides中的方法进行了调用, 接下来就一起来看一下尤大的代码吧

function inject(
  key: InjectionKey<any> | string,
  defaultValue?: unknown,
  treatDefaultAsFactory = false
) {
  // fallback to `currentRenderingInstance` so that this can be called in
  // a functional component
  const instance = currentInstance || currentRenderingInstance
  if (instance) {
    // #2400
    // to support `app.use` plugins,
    // fallback to appContext's `provides` if the intance is at root
    const provides =
      instance.parent == null
        ? instance.vnode.appContext && instance.vnode.appContext.provides
        : instance.parent.provides
    // 判断当前的函数是否存在于provides数组 如果存在调用当前函数, 如果不存在则判断第二个参数是否存在, 如果第二个参数存在 判断第二个参数是否为一个函数, 如果为函数则调用这个函数, 否则直接返回第二个参数 如果第二个参数不存在则返回一段警告
    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()
        : defaultValue
    } else if (__DEV__) {
      warn(`injection "${String(key)}" not found.`)
    }
  } else if (__DEV__) {
    warn(`inject() can only be used inside setup() or functional components.`)
  }
}

在inject函数中, 该函数接收三个参数, 第一个参数为你要调用的provide方法的key, 通过provides去调用当前的key值所对应的方法, 第二个参数为一个默认值, 第三个参数是一个选填的布尔值, 咱们接着往下分析,

  1. 代码中首先当前实例是否为根组件, 接着判断了provides中是否存在当前需要调用函数的key, 如果存在则返回这个函数的执行结果,
  2. 如果不存在则判断inject接收参数的数量, 这时候就要看第二个参数和第三个参数了, 第三个参数treatDefaultAsFactory更像是一个开关, 用来控制是否需要第二个参数,
  3. 如果treatDefaultAsFactory为true且第二个参数是个函数, 直接返回这个函数的默认结果, 否则会直接返回第二个参数.
  4. 如果不满足上述条件则会返回一段警告.

怎么样,看完之后是不是觉得provide很简单呢😄。

本段代码位于packake/runtime-core/src/apiInject.ts。有兴趣的去看一下吧。