# 依赖注入

Props 逐级传递存在的问题:

image-20240715135642336

使用 Pinia 能够解决该问题,但是如果不用 Pinia 呢?

可以用依赖注入。

# 快速上手

整个依赖注入分为两个角色:

  1. 提供方:负责提供数据
  2. 注入方:负责接收数据

1. 提供方

要提供数据,可以使用 provide 方法。例如:

<script setup>
import { provide } from "vue";

provide(/* 数据名称 */ "message", /* 实际数据 */ "hello!");
provide("message", "hello!");
</script>
1
2
3
4
5
6

该方法接收的参数也很简单:

  1. 数据对应的名称
  2. 实际的数据

2. 注入方

注入方通过 inject 方法来取得数据。例如:

<script setup>
import { inject } from "vue";

const message = inject("message");
</script>
1
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
import { inject } from "vue";

export default {
  setup() {
    const message = inject("message");
    return { message };
  },
};
1
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

在应用级别提供的数据在该应用内的所有组件中都可以注入。

3. 注入默认值

注入方可以提供一个默认值,这一点类似于 props 的默认值。

// 如果没有祖先组件提供 "message"
// value 会是 "这是默认值"
const value = inject("message", "这是默认值");
1
2
3

4. 提供响应式数据

提供方所提供的值可以是任意类型的值包括响应式的值

注意点:

  1. 如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。

  2. 尽可能将任何对响应式状态的变更都保持在提供方组件中

    <!-- 在供给方组件内 -->
    <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
  3. 使用 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
// 在供给方组件中
import { provide } from "vue";
import { myInjectionKey } from "./keys.js";

provide(myInjectionKey, {
  /* 要提供的数据 */
});
1
2
3
4
5
6
7
// 注入方组件
import { inject } from "vue";
import { myInjectionKey } from "./keys.js";

const injected = inject(myInjectionKey);
1
2
3
4
5

实战案例:整个应用程序在多个组件中共享一些全局配置(主题颜色、用户信息...)