首页 / Vue3 入门教程 / Provide 与 Inject

Vue3 入门教程

Provide 与 Inject

本教程共 40 篇 · 第 19 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

VueVue3 入门教程ProvideInject依赖注入Prop Drilling

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
兄弟通信共同父组件中转
Tip

Provide / Inject 不是状态管理工具的替代品。它适合”组件树内局部共享”,不适合全局应用状态。

本节回顾

  • Provide / Inject 解决 Prop Drilling 问题
  • provide(key, value) 在祖先组件提供数据
  • inject(key, defaultValue) 在后代组件注入
  • 传递响应式数据能保持响应连接
  • 推荐把修改方法一起 provide 给后代调用
  • Symbol 键能避免命名冲突