# 依赖注入
Props 逐级传递存在的问题:

使用 Pinia 能够解决该问题,但是如果不用 Pinia 呢?
可以用依赖注入。
# 快速上手
整个依赖注入分为两个角色:
- 提供方:负责提供数据
- 注入方:负责接收数据
1. 提供方
要提供数据,可以使用 provide 方法。例如:
<script setup>
import { provide } from "vue";
provide(/* 数据名称 */ "message", /* 实际数据 */ "hello!");
provide("message", "hello!");
</script>
1
2
3
4
5
6
2
3
4
5
6
该方法接收的参数也很简单:
- 数据对应的名称
- 实际的数据
2. 注入方
注入方通过 inject 方法来取得数据。例如:
<script setup>
import { inject } from "vue";
const message = inject("message");
</script>
1
2
3
4
5
2
3
4
5
# 相关细节
1. 非 setup 语法糖
如果没有使用 setup 语法糖,那么需要保证 provide 和 inject 方法是在 setup 方法中同步调用的:
import { provide } from "vue";
export default {
setup() {
provide(/* 注入名 */ "message", /* 值 */ "hello!");
},
};
1
2
3
4
5
6
7
2
3
4
5
6
7
import { inject } from "vue";
export default {
setup() {
const message = inject("message");
return { message };
},
};
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
因为 Vue 的依赖注入机制需要在组件初始化期间同步建立依赖关系,这样可以确保所有组件在渲染之前就已经获取到必要的依赖数据。如果 provide 和 inject 在 setup 之外或异步调用,Vue 无法保证组件初始化完成之前所有的依赖关系已经正确建立。
2. 全局依赖提供
// main.js
import { createApp } from "vue";
const app = createApp({});
app.provide(/* 注入名 */ "message", /* 值 */ "hello!");
1
2
3
4
5
6
2
3
4
5
6
在应用级别提供的数据在该应用内的所有组件中都可以注入。
3. 注入默认值
注入方可以提供一个默认值,这一点类似于 props 的默认值。
// 如果没有祖先组件提供 "message"
// value 会是 "这是默认值"
const value = inject("message", "这是默认值");
1
2
3
2
3
4. 提供响应式数据
提供方所提供的值可以是任意类型的值,包括响应式的值。
注意点:
如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。
尽可能将任何对响应式状态的变更都保持在提供方组件中
<!-- 在供给方组件内 --> <script setup> import { provide, ref } from "vue"; // 响应式数据 const location = ref("North Pole"); // 修改响应式数据的方法 function updateLocation() { location.value = "South Pole"; } provide("location", { location, updateLocation, }); </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- 在注入方组件 --> <script setup> import { inject } from "vue"; // 同时拿到响应式数据,以及修改该数据的方法 const { location, updateLocation } = inject("location"); </script> <template> <button @click="updateLocation">{{ location }}</button> </template>
1
2
3
4
5
6
7
8
9
10使用 readonly 来提供只读值
<script setup> import { ref, provide, readonly } from "vue"; const count = ref(0); provide("read-only-count", readonly(count)); </script>
1
2
3
4
5
6
5. 使用 Symbol 作为数据名
大型的应用建议最好使用 Symbol 来作为注入名以避免潜在的冲突。推荐在一个单独的文件中导出这些注入名 Symbol:
// keys.js
export const myInjectionKey = Symbol();
1
2
2
// 在供给方组件中
import { provide } from "vue";
import { myInjectionKey } from "./keys.js";
provide(myInjectionKey, {
/* 要提供的数据 */
});
1
2
3
4
5
6
7
2
3
4
5
6
7
// 注入方组件
import { inject } from "vue";
import { myInjectionKey } from "./keys.js";
const injected = inject(myInjectionKey);
1
2
3
4
5
2
3
4
5
实战案例:整个应用程序在多个组件中共享一些全局配置(主题颜色、用户信息...)