Vue3.0源码学习——Provide / Inject

1,464 阅读2分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。

Provide / Inject 文档

Composition API中使用Provide / Inject

Provide / Inject 使用场景

通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。对于这种情况,我们可以使用一对 provideinject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。

图片.png

在SFC页面 setup 中使用 Provide / Inject

  • 例如有这样层次的一个组件
Root
└─ Parent
   └─ Son
      └─ GrandSon
  • 父组建 ./Parent.vueprovide 提供一个 msg 的响应式数据,如果要确保通过 provide 传递的数据不会被 inject 的组件更改,可以使用 readonly
<template>
  <Son />
</template>

<script>
import { ref, provide,readonly } from 'vue'
import Son from './son.vue'

export default {
  components: {
    Son
  },
  setup() {
    const msg = ref('I'm from parent')
    provide('msg', readonly(msg))
  }
}
</script>
  • 儿子组件 ./Son.vue,又使用了一个组件 GrandSon
<template>
  <GrandSon />
</template>
<script>
import GrandSon from './MyMarker.vue'

export default {
  components: {
    GrandSon
  }
}
</script>
  • 孙子组件 ./GrandSon.vueinject 接收从上层传入的 msg 数据,当上层的数据发生变化,这里也会响应做出变化
<script>
import { inject } from 'vue'

export default {
  setup() {
    const userParent = inject('msg')

    return {
      userParent
    }
  }
}
</script>

源码解析

  • provide,默认情况下,实例继承父类的 provides 对象,如果当前组件有自己的 provide,它使用 父provides 对象作为原型来创建自己的 provides 对象,在 inject 只需要查询原型链就行
export function provide<T>(key: InjectionKey<T> | string | number, value: T) {
  if (!currentInstance) {
    ...
  } else {
    let provides = currentInstance.provides
    // by default an instance inherits its parent's provides object
    // but when it needs to provide values of its own, it creates its
    // own provides object using parent provides object as prototype.
    // this way in `inject` we can simply look up injections from direct
    // parent and let the prototype chain do the work.
    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
  }
}
  • inject, 查询父级的的 provides,如果实例是在根目录,回退到appContext的 provides
export 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

    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
    } else if (__DEV__) {
      ...
    }
  } else if (__DEV__) {
    ...
  }
}