Provide 与 Inject
本教程共 40 篇 · 第 19 篇 · 更新于 2026-07-29 · 约 7 分钟阅读
19. Provide 与 Inject
本节目标:学会用 Provide / Inject 跨层级传递数据,摆脱层层传递 prop 的困境。
Prop Drilling 问题
假设有一个深层嵌套的组件树,最底层的组件需要一个来自顶层的数据。用 props 的话,中间每一层都得声明并传递这个 prop——即使它们根本用不到。
<App>
<Layout> <!-- 声明并传递,自己不用 -->
<Content> <!-- 声明并传递,自己不用 -->
<DeepChild /> <!-- 真正要用的 -->
</Content>
</Layout>
</App>
这种”为了传而传”的模式叫 Prop Drilling(属性透传)。组件树越深越痛苦。
Provide / Inject 的解法(Solution)
Provide / Inject(依赖注入)就像”传送门”——祖先组件直接提供数据,后代组件直接注入,跳过中间所有层级。
<!-- 祖先组件 -->
<script setup>
import { provide } from 'vue'
provide('message', 'hello!')
</script>
<!-- 深层后代组件 -->
<script setup>
import { inject } from 'vue'
const message = inject('message')
// 'hello!'
</script>
中间组件完全不需要参与。
Provide 的用法
provide() 接受两个参数:键(key)和值(value)。
键可以是字符串或 Symbol:
import { provide } from 'vue'
// 字符串键
provide('message', 'hello!')
// 提供多个值
provide('user', { name: '张三', age: 25 })
provide('theme', 'dark')
值可以是任何类型,包括响应式的 ref:
import { ref, provide } from 'vue'
const count = ref(0)
provide('count', count)
提供响应式数据后,后代组件注入后能保持响应式连接。
应用级别 Provide
可以在 createApp() 之后调用 app.provide(),全局所有组件都能注入:
import { createApp } from 'vue'
const app = createApp({})
app.provide('apiUrl', 'https://api.example.com')
插件开发经常用这个方法。
Inject 的用法
inject() 第一个参数是键,第二个参数是默认值:
import { inject } from 'vue'
// 基础注入
const message = inject('message')
// 带默认值
const theme = inject('theme', 'light')
// 默认值用工厂函数(避免不必要的计算)
const user = inject('user', () => ({ name: '匿名' }), true)
第三个参数 true 表示把默认值当函数调用。工厂函数适合创建复杂对象或类实例。
Note如果父链中多个组件用同一个键 provide,inject 取的是最近的那一个。
保持响应性(Maintaining Reactivity)
Provide 的响应式数据,后代注入后修改会反映到祖先吗?技术上会,但不推荐这样做。
最佳实践是:把修改逻辑也 provide 出去。
<!-- 祖先组件 -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('北极')
function updateLocation() {
location.value = '南极'
}
provide('location', {
location,
updateLocation
})
</script>
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
这样数据状态的修改逻辑留在祖先组件里,后代只触发,不直接改。
只读保护(Read-only Protection)
如果不希望后代修改数据,用 readonly() 包装:
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
使用 Symbol 键(Symbol Keys)
项目大了,字符串键容易撞名。用 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)
Symbol 唯一,不用担心和其他组件冲突。
使用场景对比
| 场景 | 推荐方式 |
|---|---|
| 父子通信 | Props / Events |
| 祖孙跨层 | Provide / Inject |
| 全局共享 | Pinia / Vuex |
| 兄弟通信 | 共同父组件中转 |
TipProvide / Inject 不是状态管理工具的替代品。它适合”组件树内局部共享”,不适合全局应用状态。
本节回顾
- Provide / Inject 解决 Prop Drilling 问题
provide(key, value)在祖先组件提供数据inject(key, defaultValue)在后代组件注入- 传递响应式数据能保持响应连接
- 推荐把修改方法一起 provide 给后代调用
- Symbol 键能避免命名冲突