提供 / 注入
本页假设你已经阅读过 组件基础。如果你是组件新手,请先阅读那一页。
Prop 逐级传递
通常,当我们需要将数据从父组件传递给子组件时,会使用 props。然而,设想这样一种情况:我们有一个很大的组件树,而某个深层嵌套的组件需要来自远处祖先组件的某些内容。仅靠 props,我们就不得不把同一个 prop 一路传过整个父链:

请注意,虽然 <Footer> 组件可能根本不关心这些 props,但它仍然需要声明并继续传递它们,只是为了让 <DeepChild> 能够访问到它们。如果父链更长,那么沿途会有更多组件受到影响。这被称为“prop 逐级传递(props drilling)”,处理起来确实不太愉快。
我们可以通过 provide 和 inject 来解决 prop 逐级传递的问题。父组件可以作为所有后代组件的依赖提供者。后代树中的任何组件,无论嵌套多深,都可以注入由其父链上方组件提供的依赖。

提供
要向组件的后代提供数据,请使用 provide() 函数:
vue
<script setup>
import { provide } from 'vue'
provide(/* key */ 'message', /* value */ 'hello!')
</script>如果不使用 <script setup>,请确保在 setup() 内同步调用 provide():
js
import { provide } from 'vue'
export default {
setup() {
provide(/* key */ 'message', /* value */ 'hello!')
}
}provide() 函数接受两个参数。第一个参数称为注入键,可以是字符串或 Symbol。子组件会使用这个注入键来查找要注入的目标值。单个组件可以通过不同的注入键多次调用 provide(),以提供不同的值。
第二个参数是所提供的值。这个值可以是任意类型,包括诸如 refs 之类的响应式状态:
js
import { ref, provide } from 'vue'
const count = ref(0)
provide('key', count)提供响应式值可以让使用该值的后代组件与提供者组件建立响应式连接。
应用级提供
除了在组件中提供数据之外,我们还可以在应用级别提供:
js
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* key */ 'message', /* value */ 'hello!')应用级提供可以在应用中渲染的所有组件中使用。这在编写 插件 时尤其有用,因为插件通常无法通过组件来提供值。
注入
要注入由祖先组件提供的数据,请使用 inject() 函数:
vue
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>如果多个父级使用相同的键提供数据,inject 会解析为组件父链中最近的父级所提供的值。
如果提供的值是一个 ref,它会原样注入,并且不会自动解包。这使得注入组件能够保持与提供者组件的响应式连接。
同样地,如果不使用 <script setup>,inject() 也应只在 setup() 内同步调用:
js
import { inject } from 'vue'
export default {
setup() {
const message = inject('message')
return { message }
}
}注入默认值
默认情况下,inject 假定注入键已经在父链中的某处被提供。如果该键未被提供,将会出现运行时警告。
如果我们希望注入的属性在可选提供者存在时也能正常工作,就需要像 props 一样声明默认值:
js
// 如果没有提供与 "message" 匹配的数据
// `value` 将会是 "default value"
const value = inject('message', 'default value')在某些情况下,默认值可能需要通过调用函数或实例化一个新类来创建。为了避免在可选值未被使用时产生不必要的计算或副作用,我们可以使用工厂函数来创建默认值:
js
const value = inject('key', () => new ExpensiveClass(), true)第三个参数表示默认值应被视为工厂函数。
处理响应性
在使用响应式的 provide / inject 值时,建议尽可能将对响应式状态的任何修改都保留在_提供者_内部。这样可以确保所提供的状态及其可能的修改都位于同一个组件中,便于将来维护。
有时我们可能需要从注入组件中更新数据。在这种情况下,我们建议提供一个负责修改状态的函数:
vue
<!-- 在提供者组件内 -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>vue
<!-- 在注入者组件中 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>最后,如果你想确保通过 provide 传递的数据不能被注入组件修改,可以用 readonly() 包裹提供的值。
vue
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>使用 Symbol 键
到目前为止,我们在示例中一直使用字符串注入键。如果你正在开发一个拥有许多依赖提供者的大型应用,或者你正在编写会被其他开发者使用的组件,那么最好使用 Symbol 注入键,以避免潜在的冲突。
建议将这些 Symbols 导出到一个专门的文件中:
js
export const myInjectionKey = Symbol()js
// 在提供者组件中
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, {
/* 要提供的数据 */
})js
// 在注入者组件中
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)另请参见:类型化 Provide / Inject