概况

provide / inject(跨层级,但也适用父子)。适合深层嵌套,避免 props 逐层传递:

<!-- 父组件(祖先) -->
<script setup>
import { provide, ref } from 'vue'

const theme = ref('dark')
provide('theme', theme)
</script>
<!-- 子/孙组件 -->
<script setup>
import { inject } from 'vue'

const theme = inject('theme')
</script>

一、通信

对比项 props/emit provide/inject
传递层级 只能父子 任意层级
代码量 层级深时繁琐 简洁
数据来源 明确 隐式,不易追踪
适用场景 直接父子通信 跨层级、全局配置、主题、权限等

1、祖父 → 孙子传数据

写法

GrandParent
  └── Parent
        └── Child  ← 想直接拿到 GrandParent 的数据
<!-- GrandParent.vue(提供数据) -->
<script setup>
import { provide, ref } from 'vue'

const username = ref('zs')
provide('username', username)  // key: 'username', value: ref
</script>

<template>
  <Parent />
</template>
<!-- Parent.vue(中间层,不需要做任何事) -->
<template>
  <Child />
</template>
<!-- Child.vue(注入数据) -->
<script setup>
import { inject } from 'vue'

const username = inject('username')  // 拿到的是响应式 ref
</script>

<template>
  <div>Hello, {{ username }}</div>  <!-- 显示:Hello, zs-->
</template>

当然也可以省略Parent

GrandParent(provide)
  └── Child(inject)  ← 直接注入,不需要中间层
<!-- GrandParent.vue -->
<script setup>
import { provide, ref } from 'vue'

const username = ref('zs')
provide('username', username)
</script>

<template>
  <Child />  <!-- 直接用 Child,没有中间层 -->
</template>
<!-- Child.vue -->
<script setup>
import { inject } from 'vue'

const username = inject('username')
</script>

设置默认值(inject 找不到时的兜底)

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

// 第二个参数是默认值
const username = inject('username', 'Guest')
const config = inject('config', () => ({ size: 14 }), true) // 第三个参数 true 表示默认值是工厂函数
</script>

只读保护(防止子组件乱改数据)

<!-- 祖先组件 -->
<script setup>
import { provide, ref, readonly } from 'vue'

const count = ref(0)

provide('count', readonly(count))      // 子组件只能读
provide('increment', () => count.value++) // 只暴露方法来修改
</script>

2、子修改祖先的数据

provide 不仅可以传数据,还可以传方法,让子组件调用来修改祖先的数据:

<!-- GrandParent.vue -->
<script setup>
import { provide, ref } from 'vue'

const theme = ref('light')

function toggleTheme() {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}

provide('theme', theme)           // 提供数据
provide('toggleTheme', toggleTheme) // 提供修改方法
</script>

<template>
  <div :class="theme">
    <Child />
  </div>
</template>
<!-- Child.vue -->
<script setup>
import { inject } from 'vue'

const theme = inject('theme')
const toggleTheme = inject('toggleTheme')
</script>

<template>
  <div>
    <p>当前主题:{{ theme }}</p>
    <button @click="toggleTheme">切换主题</button>
  </div>
</template>

二、语法

inject 是 Vue 中的一个 依赖注入(Dependency Injection)机制,用于:

跨层级组件传值(祖先组件 → 后代组件)

它通常和 provide 配对使用。

作用是:

避免一层一层通过 props 往下传(prop drilling)。


1. 为什么需要 inject?

正常父子通信:

App
 └── Parent
      └── Child
           └── GrandChild

如果 App 的数据要给 GrandChild

通常要:


App → Parent → Child → GrandChild

一级一级:


<Parent :theme="theme" />

再:


<Child :theme="theme" />

再:


<GrandChild :theme="theme" />

很麻烦。

于是 Vue 提供:

provide / inject

让后代组件直接拿。


2. 基本用法

祖先组件 provide


<script>
export default {
  provide() {
    return {
      theme: 'dark'
    }
  }
}
</script>

后代组件 inject


<script>
export default {
  inject: ['theme'],

  created() {
    console.log(this.theme)
  }
}
</script>

输出:


dark

即使中间隔了很多层组件,也能拿到。


3. 更完整例子

App.vue


<script>
export default {
  provide() {
    return {
      username: 'Tom'
    }
  }
}
</script>

<template>
  <Parent />
</template>

Parent.vue


<template>
  <Child />
</template>

不需要传 props。


Child.vue


<script>
export default {
  inject: ['username']
}
</script>

<template>
  <div>{{ username }}</div>
</template>

页面:


Tom

4. inject 和 props 的区别

对比 props inject
通信方式 父 → 子 祖先 → 任意后代
是否逐层传递 不需要
是否明确 明确 隐式
推荐场景 普通组件通信 全局/跨层级共享

5. 常见场景

① UI 组件库

比如:


<Form>
   <FormItem>
      <Input />
   </FormItem>
</Form>

Input 要知道:

  • label 宽度
  • 校验状态
  • form 配置

难道一级级 props?

不会。

通常:


provide('formContext')
inject('formContext')

Element Plus、Ant Design Vue 都大量使用。


② 全局配置

例如:

主题:


theme
locale
size
permission

③ 插件开发

Vue Router、Pinia、i18n 底层也用了类似机制。

比如:


app.provide(routerKey, router)

然后:


inject(routerKey)

拿 router 实例。


6. 响应式问题(重要)

很多人踩坑。

错误理解

以为:


provide() {
  return {
    count: 0
  }
}

改了会响应更新。

不一定。

因为普通值不是响应式共享。


正确方式(Vue3)

ref/reactive


import { ref } from 'vue'

const count = ref(0)

provide('count', count)

子组件:


const count = inject('count')

直接响应式更新。


7. Vue2 写法


provide() {
  return {
    user: this.user
  }
}

但:


this.user = 'new'

可能不更新。

Vue2 对 provide 响应式支持比较弱。

通常要:


provide() {
  return {
    state: this
  }
}

然后:


inject: ['state']

访问:


state.user

8. 一句话理解

props 是:

父组件显式传参

inject 是:

后代组件向祖先“拿依赖”

有点像 Java/Spring:


@Autowired
UserService userService;

祖先:


@Bean

后代:


@Inject

所以很多人把 Vue 的 provide/inject 理解成:

轻量级依赖注入(DI)机制

更多推荐