Vue3 provide/inject 详细组件关系说明
一、组件层级关系(家族关系)
1.1 组件结构说明
text
App.vue (爷爷组件)
↓
ParentComponent.vue (爸爸组件)
↓
ChildComponent.vue (儿子组件)
↓
GrandchildComponent.vue (孙子组件)
1.2 文件结构
text
src/
├── App.vue (爷爷)
├── components/
│ ├── ParentComponent.vue (爸爸)
│ ├── ChildComponent.vue (儿子)
│ └── GrandchildComponent.vue (孙子)
二、详细代码示例(带完整引入关系)
2.1 爷爷组件 (App.vue) - 提供数据
vue
<template>
<div class="app">
<h2>我是爷爷组件 (App.vue)</h2>
<p>我提供了 userInfo 和 theme 数据</p>
<!-- 直接引入爸爸组件,不需要知道孙子组件的存在 -->
<ParentComponent />
</div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
// 只需要引入直接子组件(爸爸组件)
import ParentComponent from './components/ParentComponent.vue'
// 1. 准备要提供的数据
const userInfo = ref({
name: '张三',
age: 25,
email: 'zhangsan@email.com'
})
const theme = ref('dark')
// 2. 提供数据给所有子孙组件(包括儿子、孙子、曾孙...)
// 这些数据可以被任何层级的后代组件获取,不需要层层传递
provide('userInfo', userInfo)
provide('theme', theme)
// 在爷爷组件中也可以修改数据,所有注入的地方都会自动更新
const changeUser = () => {
userInfo.value.name = '李四'
userInfo.value.age = 30
}
</script>
2.2 爸爸组件 (ParentComponent.vue) - 中间层
vue
<template>
<div class="parent" :class="theme">
<h3>我是爸爸组件</h3>
<p>我既不需要爷爷的数据,也不提供数据,我只是个中间人</p>
<!-- 引入儿子组件 -->
<ChildComponent />
</div>
</template>
<script setup lang="ts">
// 爸爸组件不需要使用 provide/inject
// 它只是负责渲染儿子组件,完全不知道数据传递的事情
import ChildComponent from './ChildComponent.vue'
</script>
<style scoped>
.parent {
padding: 20px;
margin: 10px;
border: 2px solid #ccc;
}
.light { background: #f5f5f5; color: #000; }
.dark { background: #333; color: #fff; }
</style>
2.3 儿子组件 (ChildComponent.vue) - 也可以注入数据
vue
<template>
<div class="child">
<h4>我是儿子组件</h4>
<p>我注入了 theme:{{ theme }}</p>
<p>但我没有注入 userInfo,所以看不到用户信息</p>
<!-- 引入孙子组件 -->
<GrandchildComponent />
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
import GrandchildComponent from './GrandchildComponent.vue'
// 儿子组件可以选择性地注入需要的数据
// 这里只注入了 theme,没有注入 userInfo
const theme = inject('theme')
</script>
<style scoped>
.child {
padding: 15px;
margin: 10px;
border: 1px solid #666;
background: #e0e0e0;
}
</style>
2.4 孙子组件 (GrandchildComponent.vue) - 注入并使用数据
vue
<template>
<div class="grandchild" :class="theme">
<h5>我是孙子组件</h5>
<!-- 直接使用注入的数据 -->
<div class="user-card">
<h3>用户信息</h3>
<p>姓名:{{ userInfo.name }}</p>
<p>年龄:{{ userInfo.age }}</p>
<p>邮箱:{{ userInfo.email }}</p>
</div>
<p>当前主题:{{ theme }}</p>
<button @click="updateUser">修改用户信息</button>
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
// 定义类型接口(TypeScript)
interface UserInfo {
name: string
age: number
email: string
}
// 孙子组件注入爷爷组件提供的数据
// 注意:这里不需要引入爷爷组件!
const userInfo = inject<UserInfo>('userInfo')!
const theme = inject('theme')
// 可以直接修改响应式数据(会影响到所有使用该数据的地方)
const updateUser = () => {
userInfo.name = '王五'
userInfo.age = 35
// 这个修改会反映到所有注入了 userInfo 的组件中
}
</script>
<style scoped>
.grandchild {
padding: 10px;
margin: 10px;
border: 1px dashed #999;
}
.light { background: #fff; color: #000; }
.dark { background: #222; color: #fff; }
.user-card {
border: 1px solid #ccc;
padding: 10px;
margin: 10px 0;
}
</style>
三、关键问题解答
3.1 需要互相引入组件吗?
不需要! provide/inject 的神奇之处就在这里:
-
爷爷组件 → 只需要引入直接子组件 (
ParentComponent
) -
孙子组件 → 完全不需要知道爷爷组件的存在
-
数据流动:通过 Vue 的依赖注入系统,不需要显式引入
3.2 数据传递路径
text
App.vue (provide)
↓ (Vue内部依赖注入系统)
GrandchildComponent.vue (inject)
跳过:ParentComponent.vue 和 ChildComponent.vue
3.3 实际运行效果
在浏览器中你会看到:
text
我是爷爷组件 (App.vue)
我提供了 userInfo 和 theme 数据
我是爸爸组件
我既不需要爷爷的数据,也不提供数据,我只是个中间人
我是儿子组件
我注入了 theme:dark
但我没有注入 userInfo,所以看不到用户信息
我是孙子组件
用户信息
姓名:张三
年龄:25
邮箱:zhangsan@email.com
当前主题:dark
[修改用户信息按钮]
四、更直观的例子:主题切换
4.1 爷爷组件提供主题切换功能
vue
<!-- App.vue -->
<template>
<div class="app" :class="theme">
<h2>主题示例</h2>
<button @click="toggleTheme">切换主题</button>
<ParentComponent />
</div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import ParentComponent from './components/ParentComponent.vue'
const theme = ref('light')
// 提供主题数据
provide('theme', theme)
// 提供切换主题的方法
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
provide('toggleTheme', toggleTheme)
</script>
<style>
.light { background: white; color: black; }
.dark { background: #1a1a1a; color: white; }
</style>
4.2 孙子组件使用主题功能
vue
<!-- GrandchildComponent.vue -->
<template>
<div class="grandchild">
<p>当前主题:{{ theme }}</p>
<button @click="toggleTheme">在孙子组件切换主题</button>
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
const theme = inject('theme')
const toggleTheme = inject('toggleTheme') as () => void
</script>
五、总结
-
组件引入:只需要引入直接子组件,不需要跨层级引入
-
数据传递:通过 Vue 内部系统,不需要 props 层层传递
-
选择性注入:后代组件可以选择需要哪些数据
-
响应式:修改注入的数据会全局更新
-
类型安全:使用 TypeScript 确保类型正确
这就是 provide/inject 的强大之处------解耦组件关系,让深层嵌套的组件能直接访问需要的数据!