provide 意为「提供」,inject 意为「注入」,它们主要通过 provide 在组件内部提供一个子组件能够访问的数据,然后子组件通过 inject 在内部注入数据。

我们都知道我们在开发 Vue 项目的时候,都是以视图为线索的,把视图分割为无数的碎片(也就是组件,下文都会说是碎片)。然后把无数的碎片组合成我们想要的样子和功能。
碎片和碎片之间相互嵌套就会产生依赖关系,最终组合成一个页面交互的整体,这个整体就是组件树。
被嵌套的组件数据来源可能源于父组件,这样组件就需要通过 prop 进行定义配置。
原则上,组件是可以无限制的被依赖,数据也可以无限制的被传递下去,这就是形成了正常的单向数据流。
但是无限制的嵌套关系就会导致数据将穿过所有依赖关系中的组件,也就造成了许多层组件并未使用的数据出现。如果想要解决这个问题,就应该尽量让组件的依赖关系变得简单,组件之间的嵌套关系不能太深,组件化设计的时候就要考虑到组件的扁平化!!!
image.png
但是某些复杂的项目,我们又不得不嵌套的非常深,这个时候 provide/inject 就登场啦!

简单说就是父组件通过 provide 提供数据,子组件通过 inject 注入数据。
image.png

但是 provide/inject 又存在着很大的弊端:
1、父组件 provide 一个数据,无论哪个层级的子组件通过 inject 注入数据,这个数据都不是响应式的;
2、父组件不知道哪个子组件使用了 provide 的数据,子组件也不会知道谁提供了 inject 的数据;

使用案例

那么 provide/inject 如何做呢?
1、父组件 provide 提供一个数据

  1. export default {
  2. provide: {
  3. message: 'hello!'
  4. }
  5. }

假如我们不想直接把message的内容定义好,而是要获取data中的某个属性,那么provide就不能是一个对象!!!而是改用函数的方式,这是因为data的数据是可变的,因为对象赋值是通过引用赋值的,当data中的数据发送变化时provide也会跟着发送变化,这样就会影响子组件的依赖数据!

  1. export default {
  2. data() {
  3. return {
  4. message: 'hello!'
  5. }
  6. },
  7. provide: {
  8. // ❌❌❌
  9. // 这将会导致报错,因为无法获取到 this
  10. message: this.message
  11. }
  12. }
  1. export default {
  2. data() {
  3. return {
  4. message: 'hello!'
  5. }
  6. },
  7. provide() {
  8. // ✅✅✅
  9. // 使用函数的形式,可以访问到 `this`
  10. return {
  11. message: this.message
  12. }
  13. }
  14. }

2、子组件通过 inject 来注入父组件提供的数据

  1. export default {
  2. inject: ['message'],
  3. created() {
  4. console.log(this.message) // "hello"
  5. }
  6. }

而这里的inject数据时可以直接在data中拿到的,例如:

  1. export default {
  2. inject: ['message'],
  3. data() {
  4. return {
  5. // 基于注入值的初始数据
  6. fullMessage: this.message
  7. }
  8. }
  9. }

默认情况下,inject注入名会被某个父组件提供。但如果该注入名的确没有任何组件提供,则会抛出一个运行时警告。
如果在注入一个值时不要求必须有提供者,那么我们应该声明一个默认值,和props类似:

  1. export default {
  2. // 当声明注入的默认值时
  3. // 必须使用对象形式
  4. inject: {
  5. message: {
  6. from: 'message', // 当与原注入名同名时,这个属性是可选的
  7. default: 'default value'
  8. },
  9. user: {
  10. // 对于非基础类型数据,如果创建开销比较大,或是需要确保每个组件实例
  11. // 需要独立数据的,请使用工厂函数
  12. default: () => ({ name: 'John' })
  13. }
  14. }
  15. }

配合响应式

上面我们说了,provide提供的数据默认是不会响应式的,如果你想要实现响应式,你需要使用computed()函数提供一个计算属性:

  1. export default {
  2. data() {
  3. return {
  4. message: 'hello!'
  5. }
  6. },
  7. provide() {
  8. return {
  9. // 显式提供一个计算属性
  10. message: Vue.computed(() => this.message)
  11. }
  12. }
  13. }
  1. import { computed } from 'vue'
  2. export default {
  3. data() {
  4. return {
  5. message: 'hello!'
  6. }
  7. },
  8. provide() {
  9. return {
  10. // 显式提供一个计算属性
  11. message: computed(() => this.message)
  12. }
  13. }
  14. }