依赖注入

阅读此章节时,我们假设你已经读过组件基础,若你对组件还完全不了解,请先阅读它。

Prop Drilling

通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分内容。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦:

provideinject

Provide (供给)

<script setup>
import { provide } from 'vue'

provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>
import { provide } from 'vue'

export default {
  setup() {
    provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
  }
}
import { ref, provide } from 'vue'

const count = ref(0)
provide('key', count)
export default {
  provide: {
    message: 'hello!'
  }
}
export default {
  data() {
    return {
      message: 'hello!'
    }
  },
  provide() {
    // 使用函数的形式,可以访问到 `this`
    return {
      message: this.message
    }
  }
}

应用层 Provide

除了供给一个组件的数据,我们还可以在整个应用层面做供给:

import { createApp } from 'vue'

const app = createApp({})

app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')

应用级的供给在应用的所有组件中都可以注入。这在你编写插件时会特别有用,因为插件一般都不会使用组件形式来供给值。

Inject (注入)

<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>
import { inject } from 'vue'

export default {
  setup() {
    const message = inject('message')
    return { message }
  }
}
export default {
  inject: ['message'],
  created() {
    console.log(this.message) // injected value
  }
}
export default {
  inject: ['message'],
  data() {
    return {
      // 基于注入值的初始数据
      fullMessage: this.message
    }
  }
}
export default {
  inject: {
    /* 局部注入名 */ localMessage: {
      from: /* 原注入名 */ 'message'
    }
  }
}

注入的默认值 #

inject

如果在供给的一侧看来属性是可选提供的,那么注入时我们应该声明一个默认值,和 props 类似:

// 如果没有祖先组件提供 "message"
// `value` 会是 "这是默认值"
const value = inject('message', '这是默认值')
const value = inject('key', () => new ExpensiveClass())
export default {
  // 当声明注入的默认值时
  // 必须使用对象形式
  inject: {
    message: {
      from: 'message', // 当与原注入名同名时,这个属性是可选的
      default: 'default value'
    },
    user: {
      // 对于非基础类型数据,如果创建开销比较大,或是需要确保每个组件实例
      // 需要独立数据的,请使用工厂函数
      default: () => ({ name: 'John' })
    }
  }
}

配合响应性

<!-- 在 provider 组件内 -->
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})
</script>
<!-- 在 injector 组件 -->
<script setup>
import { inject } from 'vue'

const { location, updateLocation } = inject('location')
</script>

<template>
  <button @click="updateLocation">{{ location }}</button>
</template>
<script setup>
import { ref, provide, readonly } from 'vue'

const count = ref(0)
provide('read-only-count', readonly(count))
</script>
import { computed } from 'vue'

export default {
  data() {
    return {
      message: 'hello!'
    }
  },
  provide() {
    return {
      // 显式提供一个计算属性
      message: computed(() => this.message)
    }
  }
}

使用 Symbol 作注入名

至此,我们已经了解了如何使用字符串作为注入名。但如果你正在构建大型的应用程序,包含非常多的依赖供给,或者你正在编写提供给其他开发者使用的组件库,建议最好使用 Symbol 来作为注入名以避免潜在的冲突。

建议在一个单独的文件中导出这些注入名 Symbol:

// keys.js
export const myInjectionKey = Symbol()
// 在供给方组件中
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'

provide(myInjectionKey, { /*
  要供给的数据
*/ });
// 注入方组件
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'

const injected = inject(myInjectionKey)
// 在供给方组件中
import { myInjectionKey } from './keys.js'

export default {
  provide() {
    return {
      [myInjectionKey]: {
        /* 要供给的数据 */
      }
    }
  }
}
// 注入方组件
import { myInjectionKey } from './keys.js'

export default {
  inject: {
    injected: { from: myInjectionKey }
  }
}
__译者注__ [1] 在本章及后续章节中,“**提供**”将成为对应 Provide 的一个专有概念