依赖注入
什么是依赖注入?
依赖注入(Dependency Injection)是一种设计模式,用于在组件树中共享数据和方法,而不需要通过 props 一级一级传递。在 Vue3 中,我们使用 provide() 和 inject() 函数来实现依赖注入。
1. 基本用法
provide() 函数用于提供数据,inject() 函数用于注入数据。
示例:基本依赖注入
<!-- 父组件:提供数据 -->
<template>
<div>
<h1>父组件</h1>
<ChildComponent />
</div>
</template>
<script setup>
import { provide } from 'vue';
import ChildComponent from './ChildComponent.vue';
// 提供静态数据
provide('appName', 'Vue3 App');
// 提供响应式数据
const count = ref(0);
provide('count', count);
// 提供方法
function increment() {
count.value++;
}
provide('increment', increment);
</script>
子组件或孙组件:注入数据
<template>
<div>
<h2>子组件</h2>
<p>应用名称: {{ appName }}</p>
<p>计数: {{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script setup>
import { inject } from 'vue';
// 注入数据
const appName = inject('appName');
const count = inject('count');
const increment = inject('increment');
</script>
2. 注入默认值
可以为 inject() 函数提供默认值,当没有找到对应的数据时,将使用默认值。
示例:注入默认值
<script setup>
import { inject } from 'vue';
// 没有提供 appVersion 时,使用默认值
const appVersion = inject('appVersion', '1.0.0');
// 复杂默认值,使用工厂函数
const user = inject('user', () => ({
name: 'Guest',
age: 0
}));
</script>
3. 响应式依赖注入
当提供的数据是响应式的(ref 或 reactive),注入后仍然保持响应性。
示例:响应式依赖注入
<!-- 父组件 -->
<script setup>
import { ref, provide } from 'vue';
// 提供响应式数据
const user = ref({ name: 'John', age: 30 });
provide('user', user);
// 修改数据
function updateUser() {
user.value.name = 'Jane';
user.value.age = 25;
}
provide('updateUser', updateUser);
</script>
子组件
<script setup>
import { inject } from 'vue';
// 注入响应式数据
const user = inject('user');
const updateUser = inject('updateUser');
console.log(user.value.name); // John
// 调用注入的方法
updateUser();
console.log(user.value.name); // Jane
</script>
4. 只读注入
可以使用 readonly() 函数将提供的数据设置为只读,防止子组件修改。
示例:只读注入
<!-- 父组件 -->
<script setup>
import { reactive, readonly, provide } from 'vue';
const state = reactive({ count: 0 });
// 提供只读数据
provide('state', readonly(state));
// 提供修改方法
function increment() {
state.count++;
}
provide('increment', increment);
</script>
子组件
<script setup>
import { inject } from 'vue';
const state = inject('state');
const increment = inject('increment');
// 尝试修改只读数据
state.count++;
// 警告:Cannot assign to 'count' because it is a read-only property
// 只能通过提供的方法修改
increment(); // 正确
</script>
5. 依赖注入的作用域
provide() 提供的数据只在其组件树的后代组件中可用。
示例:作用域示例
<!-- Grandparent.vue -->
<script setup>
import { provide } from 'vue';
provide('grandparentData', '来自祖父组件的数据');
</script>
Parent.vue
<script setup>
import { provide } from 'vue';
provide('parentData', '来自父组件的数据');
// 可以覆盖祖父组件提供的数据
provide('grandparentData', '覆盖祖父组件的数据');
</script>
Child.vue
<script setup>
import { inject } from 'vue';
// 注入数据
const grandparentData = inject('grandparentData');
const parentData = inject('parentData');
console.log(grandparentData); // 覆盖祖父组件的数据
console.log(parentData); // 来自父组件的数据
</script>
6. 组合式 API 与选项式 API 的混合使用
可以在组合式 API 中提供数据,在选项式 API 中注入数据,反之亦然。
示例:混合使用
<!-- 组合式 API 提供数据 -->
<script setup>
import { provide } from 'vue';
provide('message', 'Hello from composition API');
</script>
选项式 API 注入数据
<script>
export default {
inject: ['message'],
mounted() {
console.log(this.message); // Hello from composition API
}
}
</script>
7. 依赖注入的最佳实践
- 用于在组件树中共享全局状态或配置
- 用于共享工具函数或服务
- 用于跨多级组件传递数据,避免 props 钻取
- 为注入的数据提供默认值,提高组件的健壮性
- 对于响应式数据,考虑使用 readonly() 防止子组件意外修改
- 使用命名空间避免命名冲突,如 'user.name'、'user.age'
8. 依赖注入 vs Props
| 特性 | 依赖注入 | Props |
|---|---|---|
| 传递方式 | 跨多级组件 | 父子组件 |
| 显式性 | 隐式,依赖关系不明确 | 显式,依赖关系明确 |
| 响应性 | 支持响应式数据 | 支持响应式数据 |
| 默认值 | 支持 | 支持 |
| 类型检查 | 需要手动实现 | 内置类型检查 |
| 适用场景 | 全局状态、工具函数、服务 | 父子组件通信 |
9. 依赖注入的注意事项
- 依赖注入会使组件之间的关系变得不明确,增加调试难度
- 过度使用依赖注入会导致组件耦合度增加
- 对于复杂的全局状态,考虑使用状态管理库(如 Pinia)
- 注入的数据是响应式的,修改会影响所有注入该数据的组件