【Vuejs】1732- 详细聊一聊 Vue3 依赖注入

news2024/10/7 14:33:47

c97247fd844b17cab3c27554c645bf84.png

👉 「相关文章」

  1. 深入浅出 Vue3 自定义指令

  2. 详细聊一聊 Vue3 动态组件

  3. 6 个你必须明白 Vue3 的 ref 和 reactive 问题

  4. 初中级前端必须掌握的 10 个 Vue 优化技巧

  5. 分享 15 个 Vue3 全家桶开发的避坑经验

在 Vue.js 中,依赖注入[1](DI)是一种非常常见的跨组件传递数据的方法,它可以帮助我们更好地管理组件之间的依赖关系。本文将介绍 Vue3 中的依赖注入机制,包括 provide()inject() 函数的使用方法、使用注意以及优缺点和适用场景等方面的内容。

如果你对“依赖注入”的概念不熟悉,可以通过《Wiki - 依赖注入[2]》链接进行了解。

✨ 快速上手

Vue3 中的依赖注入机制提供 provide()inject() 函数,用于实现「组件之间的依赖关系传递和共享」

介绍

在没有依赖注入机制之前,开发者经常会遇到「组件属性逐级透传」的问题,也就是「组件的属性需要逐层往深层子组件进行传递」,导致链路很长,非常麻烦。1e75fc2beee569afeee08fabb39107a9.png

(图片来源:Vue.js[3]

为了解决这个问题,Vue3 提供的依赖注入机制,只需要在父组件提供(provide)依赖,任何层级的后代组件注入该依赖即可。42909fb16a651128c595e16ef4e374d7.png(图片来源:Vue.js[4]

下面先介绍 provide()inject() 这两个函数的接口定义:

  • provide(name, value)

通常在父组件使用,提供一个值,可以被任意层级子组件注入。

function provide<T>(key: InjectionKey<T> | string, value: T): void;

该函数接收 2 个参数,参数name「注入的 key」,可以是「字符串」或者 Symbol,子组件通过该值来注入,参数value为需要注入的依赖值,可以是任何类型的值。

  • inject

常在子组件使用,注入一个由父组件或整个应用 (通过 app.provide()) 提供的值。

// 没有默认值
function inject<T>(key: InjectionKey<T> | string): T | undefined;

// 带有默认值
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;

// 使用工厂函数
function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: () => T,
  treatDefaultAsFactory: true
): T;

该函数接收 2 个参数,参数 key 为父组件提供值的** key**,参数defaultValue为可选参数,作为依赖的默认值,可以是具体的值,也可以是函数,来创建复杂的值,参数treatDefaultAsFactory也是可选值,当 defaultValue为函数时,需要设置treatDefaultAsFactoryfalse,表明这个函数是默认值,而不是工厂函数。

使用示例

下面是 provide()inject() 函数的使用示例:

<!-- 父组件 -->
<template>
  <child-component></child-component>
</template>
<script setup lang="ts">
import { provide } from "vue";
provide("name", "Chris");
</script>

<!-- 子组件 -->
<template>
  <div>name: {{ name }}</div>
</template>
<script setup lang="ts">
import { inject } from "vue";
const name = inject("name", "defaultName");
</script>

在上面的示例中,我们在父组件中使用 provide('name', 'Chris') 提供了一个注入名为 name 的值,值为 'Chris'。在子组件中使用 inject('name', 'defaultName')注入这个值,并赋值给变量 name,添加到模版中。

🚀 使用场景

通常有以下使用常见:

  • 「大型项目」:在大型项目中,组件之间的依赖关系比较复杂,使用依赖注入可以更好地管理这些依赖关系。

  • 「可重用性要求高的项目」:在需要重用代码的项目中,使用依赖注入可以提高代码的可重用性。

  • 「需要进行单元测试的项目」:在需要进行单元测试的项目中,使用依赖注入可以使测试更容易进行。

❓ 常见问题

使用 provide()inject() 时需要注意以下问题:

inject() 只能使用在 setup() 或函数组件中

如果没有使用 <script setup>inject() 需要在 setup() 内同步调用: 比如:

<script setup lang="ts">
import { provide } from "vue";
provide("name", "Chris");
</script>

或者:

import { inject } from 'vue' export default { setup() { const message =
inject('message') return { message } } }

provide() 注入名可以为 Symbol 类型

使用provide(name, value)name参数可以支持不同类型的值,包括:

  • 字符串:如 provide('name', 'Chris')

  • Symbol:如 provide(Symbol(), 'Chris'),当我们在开发大型且依赖多的应用时,可以使用 Symbol类型作为注入名,「避免冲突」

接下来是使用 Symbol+ TypeScript 的一个示例代码:

// key.ts
import type { InjectionKey } from "vue";
export const symbolStringKey = Symbol() as InjectionKey<string>;

// 父组件
import { provide } from "vue";
import { symbolStringKey } from "./key";
provide(symbolStringKey, "Chris");

// 子组件
import { inject } from "vue";
import { symbolNumberKey } from "./key";
const symbolNumber = inject(symbolNumberKey);

在使用 TypeScript 时,可以使用 InjectionKey泛型类型,并使用注入值的类型作为泛型参数。

provide() 注入值为响应式数据

使用provide(name, value)value 参数可以支持不同类型的值,包括:

  • 普通类型:如字符串,数字,普通对象等;

  • 响应式类型:如 Vue3 的 refreactivereadonly 等,如果是响应式数据,则该值发生变化后,有注入该值的任何层级的子组件,都会更新这个值;

接下来演示一下响应式类型的示例:

  • 父组件

<script setup lang="ts">
import { provide, ref, reactive, readonly } from "vue";
import Child1 from "./Child1.vue";

const user = { name: "Chris", age: 18 };
const userRef = ref({ name: "Chris", age: 18 });
const userReactive = reactive({ name: "Chris", age: 18 });
const userReadonly = readonly({ name: "Chris", age: 18 });

provide("name", "Chris");
provide("age", 18);
provide("user", user);
provide("userRef", userRef);
provide("userReactive", userReactive);
provide("userReadonly", userReadonly);

const changeUser = () => {
  user.name = "New Chris";
  user.age = 30;
};
const changeUserRef = () => {
  userRef.value.name = "Ref Chris";
  userRef.value.age = 30;
};
const changeUserReactive = () => {
  userReactive.name = "Reactive Chris";
  userReactive.age = 30;
};
const changeUserReadonly = () => {
  // @ts-ignore
  userReadonly.name = "Readonly Chris";
  // @ts-ignore
  userReadonly.age = 30;
};
</script>

<template>
  <div class="ProvideInject">
    <div>Root Component</div>
    <button @click="changeUser">Update user</button>
    <button @click="changeUserRef">Update userRef</button>
    <button @click="changeUserReactive">Update userReactive</button>
    <button @click="changeUserReadonly">Update userReadonly</button>
    <div>user: {{ user.name }} / {{ user.age }}</div>
    <div>userRef: {{ userRef.name }} / {{ userRef.age }}</div>
    <div>userReactive: {{ userReactive.name }} / {{ userReactive.age }}</div>
    <div>userReadonly: {{ userReadonly.name }} / {{ userReadonly.age }}</div>
    <Child1></Child1>
  </div>
</template>
  • 子组件

<script setup lang="ts">
import { inject, ref, reactive, readonly } from "vue";

const name = inject("name", "defaultName");
const age = inject("age", 20);
const user = inject("user", { name: "", age: 22 });
const userRef = inject("userRef", ref({ name: "", age: 22 }));
const userReactive = inject("userReactive", reactive({ name: "", age: 22 }));
const userReadonly = inject("userReadonly", readonly({ name: "", age: 22 }));

const changeUserRef = () => {
  userRef.value.name = "Child1 Ref Chris";
  userRef.value.age = 30;
};
const changeUserReactive = () => {
  userReactive.name = "Child1 Reactive Chris";
  userReactive.age = 30;
};
const changeUserReadonly = () => {
  // @ts-ignore
  userReadonly.name = "Child1 Readonly Chris";
  // @ts-ignore
  userReadonly.age = 30;
};
</script>

<template>
  <div class="Child1">
    <div>Child Component 1</div>
    <button @click="changeUserRef">Update userRef</button>
    <button @click="changeUserReactive">Update userReactive</button>
    <button @click="changeUserReadonly">Update userReadonly</button>
    <div>name: {{ name }}</div>
    <div>age: {{ age }}</div>
    <div>user: {{ user.name }} / {{ user.age }}</div>
    <div>userRef: {{ userRef.name }} / {{ userRef.age }}</div>
    <div>userReactive: {{ userReactive.name }} / {{ userReactive.age }}</div>
    <div>userReadonly: {{ userReadonly.name }} / {{ userReadonly.age }}</div>
  </div>
</template>

在这个示例中,父组件使用 provide() 函数提供普通对象、ref响应式对象、reactive响应式对象、readonly响应式对象,然后子组件分别注入这些依赖并将值展示在视图中。最后在父子组件分别提供按钮修改这些值,观察父子组件视图上数据的变化。 可以观察到,普通对象变化后,子组件视图并不会更新,而如果是「响应式对象」发生变化,则「子组件视图更新」

示例代码地址:https://github.com/pingan8787/Leo-JavaScript/tree/master/Cute-Vue/Demo/ProvideInject[5]

尽量在提供方组件更新响应式数据

由于响应式数据作为 provide()提供的值,可以在任意层级的子组件注入,并且修改后会响应式变化,这就导致很多时候,「我们无法知道是在哪个子组件修改了这个响应式数据」。 因此建议开发者尽量在父组件,也就是响应式数据提供方的组件进行更新数据,确保提供状态声明和变更操作都在同一个组件,方便维护。

<script setup lang="ts">
import { provide, ref, reactive, readonly } from "vue";
import Child1 from "./Child1.vue";
const userRef = ref({ name: "Chris", age: 18 });
provide("userRef", userRef);
const changeUserRef = () => {
  userRef.value.name = "Ref Chris";
  userRef.value.age = 30;
};
</script>

<template>
  <div class="ProvideInject">
    <div>Root Component</div>
    <button @click="changeUserRef">Update userRef</button>
    <div>userRef: {{ userRef.name }} / {{ userRef.age }}</div>
    <Child1></Child1>
  </div>
</template>

在这个示例代码中,父组件通过 provide()提供了 userRef响应式数据,并且通过 changeUserRef方法修改 userRef的值。 当子组件需要修改响应式数据时,可以在父组件也提供一个修改值的方法:

  • 父组件

<script setup lang="ts">
import { provide, ref, reactive, readonly } from "vue";
import Child1 from "./Child1.vue";
const userRef = ref({ name: "Chris", age: 18 });
provide("userRef", { userRef, changeUserRef });
const changeUserRef = () => {
  userRef.value.name = "Ref Chris";
  userRef.value.age = 30;
};
</script>

<template>
  <div class="ProvideInject">
    <div>Root Component</div>
    <button @click="changeUserRef">Update userRef</button>
    <div>userRef: {{ userRef.name }} / {{ userRef.age }}</div>
    <Child1></Child1>
  </div>
</template>
  • 子组件

<script setup lang="ts">
import { inject } from "vue";
const { userRef, changeUserRef } = inject("userRef");
</script>

<template>
  <div class="Child1">
    <div>Child Component 1</div>
    <button @click="changeUserRef">Update userRef</button>
    <div>userRef: {{ userRef.name }} / {{ userRef.age }}</div>
  </div>
</template>

上面示例代码中,父组件通过 provide('userRef', {userRef, changeUserRef})将修改响应式数据的方法也提供出去,子组件注入依赖后,通过解构获取到 changeUserRef 方法,即可修改该响应式数据。

使用 readonly() 让注入方无法修改提供的数据

如果开发者想让父组件提供的值数据,不能被子组件,也就是注入方修改,可以通过 Vue3 提供的 readonly()方法来包装该值,接下来看个示例代码。

  • 父组件

<script setup lang="ts">
import { provide, readonly } from "vue";
import Child1 from "./Child1.vue";
const userReadonly = readonly({ name: "Chris", age: 18 });
provide("userReadonly", userReadonly);
const changeUserReadonly = () => {
  // @ts-ignore
  userReadonly.name = "Readonly Chris";
  // @ts-ignore
  userReadonly.age = 30;
};
</script>

<template>
  <div class="ProvideInject">
    <div>Root Component</div>
    <button @click="changeUserReadonly">Update userReadonly</button>
    <div>userReadonly: {{ userReadonly.name }} / {{ userReadonly.age }}</div>
    <Child1></Child1>
  </div>
</template>
  • 子组件

<script setup lang="ts">
import { inject, readonly } from "vue";
const userReadonly = inject("userReadonly", readonly({ name: "", age: 22 }));
const changeUserReadonly = () => {
  // @ts-ignore
  userReadonly.name = "Child1 Readonly Chris";
  // @ts-ignore
  userReadonly.age = 30;
};
</script>

<template>
  <div class="Child1">
    <div>Child Component 1</div>
    <button @click="changeUserReadonly">Update userReadonly</button>
    <div>userReadonly: {{ userReadonly.name }} / {{ userReadonly.age }}</div>
  </div>
</template>

这个示例代码中,父组件使用 provide()提供的值是个 readonly()包装的值,子组件在注入之后,无法修改。

在嵌套 provide 时,存在同名的 key 会如何?

由于 provide可以无限层级的使用,经常就会出现 providekey 名称重复的情况,那么这时候 inject注入的值会变成什么?我们看看下面这个示例代码:

  • 父组件

<script setup lang="ts">
provide("name", "Chris");
// 省略其他
</script>

<template>
  <Child1></Child1>
</template>
  • 子组件

<script setup lang="ts">
provide('name', 'Child Provide')
  // 省略其他
</script>

<template>
  <Child2></Child1>
</template>
  • 孙组件

<script setup lang="ts">
const name = inject("name", "defaultName");
// 省略其他
</script>

<template>
  <div>name: {{ name }}</div>
</template>

最后可以看到视图显示的是 "name:Child Provide"。 所以当出现嵌套 provide 时,存在同名的 key 时,会优先使用最近的父组件的 provide 值。

🤔 优缺点

优点

  • 「减少组件之间的耦合度」:依赖注入可以帮助我们更好地管理组件之间的依赖关系,减少组件之间的耦合度,使代码更容易维护和扩展。

  • 「提高代码的可重用性」:依赖注入可以使代码更加模块化,提高代码的可重用性。

  • 「更容易进行单元测试」:依赖注入可以使代码更容易进行单元测试,因为我们可以用 mock 对象替代实际对象,更方便地进行测试。

缺点

  • 「增加代码的复杂度」:依赖注入需要增加一些额外的代码来实现,这会增加代码的复杂度。

  • 「可能会导致性能问题」:依赖注入可能会导致性能问题,因为它需要在运行时动态获取依赖关系。

🔍 总结

本文主要介绍了 Vue3 中的依赖注入机制,包括 provide()inject() 函数的使用方法、使用注意以及优缺点和适用场景等方面的内容。通过本文的介绍,相信读者可以更好地理解 Vue3 中的依赖注入机制,并在实际项目中进行应用。

📚 拓展资料

如果你想深入了解 Vue3 中的依赖注入机制,可以参考以下资料:

  • Vue.js - Provide / Inject[6]

  • Vue.js Internals: Understanding the Dependency Injection System[7]

  • The new Provide and Inject in Vue 3[8]

希望这些资料能够对你有所帮助!

参考资料

[1]

依赖注入: https://vuejs.org/guide/components/provide-inject.html

[2]

Wiki - 依赖注入: https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5

[3]

Vue.js: https://vuejs.org/guide/components/provide-inject.html#prop-drilling

[4]

Vue.js: https://vuejs.org/guide/components/provide-inject.html#prop-drilling

[5]

https://github.com/pingan8787/Leo-JavaScript/tree/master/Cute-Vue/Demo/ProvideInject: https://github.com/pingan8787/Leo-JavaScript/tree/master/Cute-Vue/Demo/ProvideInject

[6]

Vue.js - Provide / Inject: https://vuejs.org/guide/components/provide-inject.html

[7]

Vue.js Internals: Understanding the Dependency Injection System: https://codedamn.com/news/vuejs/vuejs-internals-dependency-injection-system

[8]

The new Provide and Inject in Vue 3: https://vuedose.tips/the-new-provide-inject-in-vue-3

往期回顾

#

如何使用 TypeScript 开发 React 函数式组件?

#

11 个需要避免的 React 错误用法

#

6 个 Vue3 开发必备的 VSCode 插件

#

3 款非常实用的 Node.js 版本管理工具

#

6 个你必须明白 Vue3 的 ref 和 reactive 问题

#

6 个意想不到的 JavaScript 问题

#

试着换个角度理解低代码平台设计的本质

008740f9a389722e3229b2150be3adbc.gif

回复“加群”,一起学习进步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/720384.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中小企业做知识管理如何选择KMS?

编者按&#xff1a;&#xff08;KM&#xff09;是创建、共享、使用和管理组织的知识和信息的过程。它是指通过充分利用知识来实现组织的多学科方法。那么中小企业预算有限的情况下&#xff0c;该如何选择KMS呢 &#xff1f; 关键词&#xff1a;知识管理系统、免安装、免维护 市…

在职读研弥补学历短板——中国人民大学与加拿大女王大学金融硕士项目

在当今社会 “文凭化”的理念下&#xff0c;学历变得很重要。学历会影响到一个人成长发展的各各方面&#xff0c;当我们“工作越久&#xff0c;接触社会越久”&#xff0c;越感觉到学历的重要性。具有高学历&#xff0c;就具有更多的发展机会&#xff0c;具有更多精神上的财富&…

Basler相机一丢包就断开问题解决

问题描述&#xff1a; 两个相机&#xff0c; 一个相机aca2500-14gm连接电脑主板100M网卡没问题&#xff0c;帧率3帧&#xff0c;但是不会断。 一个相机aca2500-14gm连接USB转网口&#xff08;千兆&#xff09;&#xff0c;pylon Viewer采图丢包严重并且几秒后相机断开。 解决…

集合面试题--复杂度分析

为什么要进行复杂度分析&#xff1f; 1指导编写出性能更优的代码2评判别人写的代码的好坏 时间复杂度分析 常见复杂度表示 常见复杂度 空间复杂度

【赠书活动 - 第1期】- 测试工程师Python开发实战(异步图书出品)| 文末送书

⭐️ 赠书 - 测试工程师Python开发实战&#xff08;异步图书出品&#xff09; 当初就是因为开发做不好&#xff0c;才去做测试了…… 这句玩笑话在过去可以说是测试人员的真实写照。 常规测试工作给人的印象&#xff0c;就是弄清楚软件功能&#xff0c;编写测试用例&#xff0…

基于springboot+Redis的前后端分离项目之消息队列(六)-【黑马点评】

&#x1f381;&#x1f381;资源文件分享 链接&#xff1a;https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwdeh11 提取码&#xff1a;eh11 秒杀优化、消息队列 秒杀优化1 秒杀优化-异步秒杀思路2 秒杀优化-Redis完成秒杀资格判断3 秒杀优化-基于阻塞队列实现秒杀优化 Red…

抖音矩阵系统源码开源部署分享(三)

目录 一、 概述&#xff1a; 二、 账号矩阵搭建目的&#xff1a; 三、 抖音矩阵系统源码开发步骤 四、 功能规划 五、 代码开发展示 一、 概述&#xff1a; 抖音矩阵系统是指通过多个账号运营&#xff0c;对账号之间的内容和特征进行细分&#xff0c;账号之间相互引流推广&a…

什么是数字化和数字化转型?终于有人讲明白了!

在我与不同行业、不同岗位甚至不同阶层的人谈论数字化和数字化转型的时候发现一个很有意思的现象&#xff1a; 许多人出于无知或为了自己的利益而开始混淆这两个术语&#xff0c;甚至一些人已经开始将数字化标记为数字化转型&#xff0c;以安抚管理层、获得项目批准或进行销售…

在海外我们该如何推广应用

Google Play和Apple Store上有各种各样不同的应用程序&#xff0c;大量的正面评论和高评级可以成为应用在当前市场上取得成功的关键。大多数用户更喜欢有很多应用评论&#xff0c;积极反馈和高评级的应用程序&#xff0c;因此每条应用程序评论都很重要。确保鼓励用户留下评论&a…

R语言学习——数据框

x c(42,7,64,9) y1:4 z.df data.frame(INDEXy, VALUEx) z.df dim(z.df) # 查看几行几列 colnames(z.df) # 查看列名 rownames(z.df) # 查看行名 z.df[,1] z.df[1,] z.df[c(1,2),c(1,2)]df1 data.frame(C1c(1,5,14,1,54), C2c(9,15,85,9,42), C3c(8,7,42,8,16)) df1 df2 <…

力扣 78. 子集

题目来源&#xff1a;https://leetcode.cn/problems/subsets/description/ C题解1&#xff1a;递归回溯法。由于是求子集&#xff0c;所以根据nums.size()遍历每个子集的长度&#xff0c;并进行回溯。 class Solution { public:vector<vector<int>> res;vector<…

Blazor前后端框架Known-V1.2.2

V1.2.2 Known是基于C#和Blazor开发的前后端分离快速开发框架&#xff0c;开箱即用&#xff0c;跨平台&#xff0c;一处代码&#xff0c;多处运行。 概述 基于C#和Blazor实现的快速开发框架&#xff0c;前后端分离&#xff0c;开箱即用。跨平台&#xff0c;单页应用&#xff…

scratch 篮球反弹

scratch 篮球反弹 本程序的功能是一个角色水平移动、碰到边缘反弹&#xff0c;“篮球”初始位置和方向随机&#xff0c;接触到其它角色或边缘时反弹。 具体内容如下 “篮球”角色 男孩角色

集成运放电路计算(全)

自记&#xff1a; 常用运放电路计算与分析 1、运放的符号表示 2、集成运算放大器的技术指标 (1) 开环差模电压放大倍数(开环增益)大 Ao(Ad)Vo/(V-V-)107-1012倍; (2) 共模抑制比高 KCMRR100db以上; (3) 输入电阻大 ri>1MW, 有的可达100MW以上; (4) 输出电阻小 ro 几W-几十…

如何将语音转换成文字?分享好用的3个方法!

为了方便制作会议记录&#xff0c;通常我们会录制会议内容&#xff0c;并在后期根据录音进行整理。然而&#xff0c;许多人在整理过程中感到痛苦&#xff0c;因为需要反复听取音频才能完成整理工作。其实&#xff0c;我们可以借助记灵在线工具将语音转换为文字&#xff0c;从而…

Rust 第一天---Rust环境配置

学习一门新的语言总是令人兴奋的,新的语法特性,设计理念…当然任何新的事物总是会留有旧事物的影子,这也能帮助我们更快地学习理解.作为2015年才正式发布的“年轻”语言,安全是它最大特性也是受欢迎原因之一.通过所有权系统进行内存管理,避免了其他高级语言因垃圾回收带来的消耗…

一百二十六、DBeaver——导入CSV文件(文件中无表字段)到ClickHouse

一、目标&#xff1a;将CSV文件的数据导入到ClickHouse中 备注&#xff1a;CSV文件没有表字段&#xff0c;只有纯粹的数据 二、实施步骤 第一步&#xff0c;右击表名&#xff0c;选择导入数据 第二步&#xff0c;在源类型和格式&#xff0c;选择从CSV文件导入&#xff0c;然…

企业如何建设积分商城?

企业建设一个成功的积分商城系统并不是一件简单的事情&#xff0c;需要注意诸多细节。我们该如何建设积分商城呢&#xff1f;作为一位电商行业十多年的从业者&#xff0c;这里分享一些经验。 一、明确商城建设目标 在建设积分商城之前&#xff0c;我们需要明确建设商城的目标&…

Flutter基础控件

Text:文字 Text("Flutter") Text是最常用也是最基础的&#xff0c;目前学习阶段只用来加载文字数据&#xff0c;更多属性和样式设置请查看源码自己探索。 Button:按钮 ElevatedButton:普通按钮 ElevatedButton(onPressed: () {if (kDebugMode) {print("Elevat…