Vue3【Provide/Inject】

前言

自从使用了Provide/Inject代码的组织方式更加灵活了,但是这个灵活性的增加伴随着代码容错性的降低。我相信只要是真的在项目中引入Provide/Inject的同学,一定一定有过或者正在经历下面的状况:

  • 注入名(Injection key)经常拼错,又或者注入名太多导致注入名取名困难(程序员通病)
  • 为了弄清楚inject()注入的是啥,不得不找到对应provide()
  • 另一种情况是重复provide()同一值,导致Injection覆盖
  • 使用inject()时祖先链上未必存在对应的provide(),不得不做空值处理或默认值处理
  • 在hook中使用provide(),但是调用hook的组件无法inject()这个hook的provide()

Provide/Inject解决了什么问题?

依赖注入|Vue.js中提到Provide/Inject这两个API主要是用来解决Prop逐级透传问题(就像下面这样)

在这里插入图片描述
引入Provide/Inject后Prop就可以直接传入到后代组件(就像下面这样)
在这里插入图片描述
根组件中通过provide提供注入值,示例代码如下:

import { provide } from 'vue';provide(/* 注入名 */ 'account', /* 值 */ { name: 'youth' });

后代组件中通过inject获取祖先组件注入的值,示例代码如下:

import { inject } from 'vue';const message = inject('account');

当只是在项目中小范围的使用provide和inject时,上面示例的写法没什么问题。但是如果项目工程较大,代码量也多的情况下,就会出现一些问题。

注入名冲突

问题是如何保证account不会被其他业务组件覆盖?例如如果某个业务组件也提供了account的信息,就像下面这样:
在这里插入图片描述

中间层的ParentView组件可能是一个用户列表组件,也提供了account数据,这里的account可能是列表选中的用户,而Main中提供的是当前用户。在DeepChild组件中可能即需要当前登录用户信息,又需要列表选中的用户信息,而目前DeepChild中只能获取到ParentView提供的选中用户信息。

当然这种业务场景有很多解决方案,这里先认为只能通过provide/inject解决

当然我们完全可以在ParentView中将注入名改写为selectAccount来解决这个问题,但是如果中间层还有其他的组件,这些组件也有selectAccount呢?

实践方案

在项目中创建一个名为injection-key.ts的文件,我习惯将该文件创建为src/constants/injection-key.ts。这样在该文件中统一管理项目下的注入名,并且使用Symbol来创建注入名,来回避取名冲突.

export const CurAccountKey = Symbol('account');export const AuthAccountKey = Symbol('account');

用法示例:

Main.vue:

import { provide } from 'vue';
import { CurAccountKey } from '@/constants/injectionKeys';const user = reactive({ id: 1, name: 'youth' });
provide(CurAccountKey, user);

ParentView.vue:

import { provide } from 'vue';
import { AuthAccountKey } from '@/constants/injectionKeys';const user = reactive({ id: 1, name: 'John Doe' });
provide(AuthAccountKey, user);

DeepChild.vue:

import { inject } from 'vue';
import { AuthAccountKey, CurAccountKey } from '@/constants/injectionKeys';const curAccount = inject(CurAccountKey);
const authAccount = inject(AuthAccountKey);

注入提示

但是使用inject(CurAccountKey)会代码什么样的数据?这就不得不全局查找CurAccountKey的provide了。这种的使用体验十分不好,这时Vue官方推荐我们使用TS。

import { inject } from 'vue';
import { AuthAccountKey, CurAccountKey } from '@/constants/injectionKeys';const curAccount = inject(CurAccountKey);
curAccount.name; // curAccount存在name吗?

实践方案

Vue|为provide / inject 标注类型中提到了InjectionKey类型,使用TS和InjectionKey可以有效解决类型提示问题

src/types.ts:

export interface Account {name: string;id: number;
};

src/constants/injection-key.ts:

import { InjectionKey } from 'vue';
import { Account } from '@/types';export const CurAccountKey: InjectionKey<Account> = Symbol('account')

Main.vue:

import { provide } from 'vue';
import { CurAccountKey } from '@/constants/injectionKeys';const user = reactive({ id: 1, name: 'youth' });
provide(CurAccountKey, 'name: youth'); // ❌
provide(CurAccountKey, user); // 💯

DeepChild.vue:

const curAccount = inject(CurAccountKey);
curAccount?.age; // ❌
curAccount?.id; // 💯

严格注入

默认情况下,inject假设传入的注入名会被某个祖先链上的组件提供。如果该注入名的确没有任何组件提供,则会抛出一个运行时警告

const curAccount = inject(CurAccountKey);
curAccount?.id;

当然有时候我们可能并不是要求必须在祖先链上提供,这时候Vue官方推荐我们使用默认值来解决祖先链未提供值的情况,这也仅仅是能解决inject值不是必要值的情况

但是有些情况下我们又要求祖先链上必须提供需要的inject,这种情况更常见的是通用型组件开发中。例如:和组件,的祖先链上必须存在组件。如果单独使用是不合法的,这时候应该抛出错误❌而不是警告⚠️

要解决上面的严格依赖问题,我们当然可以在子组件中通过判断inject的值是否为undefined,如果是则抛出异常。这种代码很简单:

const curAccount = inject(CurAccountKey);
if (!curAccount) {throw new Error('CurAccountKey必须提供对应的Provide');
}
curAccount.id;

嗯,不错!是解决了问题!如果严格依赖的很多呢?难不成到处都是if判断?

实践方案

创建一个严格注入工具函数,当对应的注入名没有被提供时抛出异常。

export const injectStrict = <T>(key: InjectionKey<T>, defaultValue?: T | (() => T), treatDefaultAsFactory?: false): T => {const result = inject(key, defaultValue, treatDefaultAsFactory); if (!result) { throw new Error(`Could not resolve ${key.description}`); } return result;
}

使用injectStrict重写吧:

const curAccount = injectStrict(CurAccountKey);
curAccount.id;

再谈逐级穿透

在Vue中Provide组件无法使用provide值

这个看着有点绕,直观来看使用情况是这样的:

const user = reactive({ id: 1, name: 'youth' });
provide(CurAccountKey, user);...inject(CurAccount); // 这里无法获取👆提供的user

这时候有的同学肯定会说,Provide组件使用provide的值?有没有搞错啊?怎么会有这种操作?

const user = reactive({ id: 1, name: 'youth' });
provide(CurAccountKey, user);//这里需要user值的时候,直接用不就好了??
user;

逐级透传问题又来了
但是,别忘了自定义hook的情况啊!!如果provide(CurAccountKey, user);是在一个自定义的hook中的呢?

useAccount.ts:

export const useAccount = async () => {const user = await fetch('/**/*');provide(CurAccountKey, user);return { user };
}

如果是直接调用useAccount还不是问题,因为useAccount返回了user。在调用userAccount的地方可以直接解构出user,这样很直观也很方便。

如果useAccount被其他的hook再次封装呢?

useApp.ts:

export const useApp = async () => {const account = await useAccount();...return {account}
}

当然,这也不是没有解决方法,可以在useApp中解构account再返回

useApp.ts:

export const useApp = async () => {const account = await useAccount();...return {...account}
}

有没有觉得这种情况很熟悉?我们把hook换成组件,情况是不是就是这样:
在这里插入图片描述
Provide/Inject的出现就是为了解决这样的问题,但是当在hook中出现透传时,却又成了最初的样子啊!

实践方案

解决上面问题的方案也很简单,就是获取当前组件实例,然后从组件实例中找到provide的值就好了!

既然Vue本身无法支持当前组件获取当前组件的provide,那我们自己实现一个吧!

import { getCurrentInstance, inject, InjectionKey } from 'vue';export const injectWithSelf = <T>( key: InjectionKey<T>): T | undefined => { const vm = getCurrentInstance() as any; return vm?.provides[key as any] || inject(key);
}

这里我们从当前组件的实例中找到对应key的provide值,如果不存在就走inject从祖先链组件中获取。

使用injectWithSelf重写一下吧:

useAccount.ts:

export const useAccount = async () => {const user = await fetch('/**/*');provide(CurAccountKey, user);return { user };
}

useApp.ts:

export const useApp = async () => {const account = await useAccount();...return {account}
}

Main.vue:

useApp();// 必须在useApp()之后
const user = injectWithSelf(CurAccountKey)

最后

  • 使用Symbol来创建注入名,来回避取名冲突
  • 使用TS和InjectionKey可以有效解决类型提示问题
  • 使用自定义injectStrict可以解决严格注入问题
  • 使用自定义injectWithSelf可以解决hook嵌套时的返回值逐级穿透问题

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

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

相关文章

C++中虚继承时的构造函数

在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。 下面…

VBA技术资料MF51:VBA_在Excel中突出显示唯一值

【分享成果&#xff0c;随喜正能量】世间万物&#xff0c;因果循环不休&#xff0c;你的善心善行&#xff0c;都可能成为你的善缘善果。每天忆佛念佛&#xff0c;每天都在佛菩萨的加持下生活&#xff0c;自然吉祥如意&#xff0c;法喜充满。 。 我给VBA的定义&#xff1a;VBA是…

重磅| Falcon 180B 正式在 Hugging Face Hub 上发布!

引言 我们很高兴地宣布由 Technology Innovation Institute (TII) 训练的开源大模型 Falcon 180B 登陆 Hugging Face&#xff01; Falcon 180B 为开源大模型树立了全新的标杆。作为当前最大的开源大模型&#xff0c;有180B 参数并且是在在 3.5 万亿 token 的 TII RefinedWeb 数…

3D点云处理:点云投影为2D图像 调平点云(附源码)

文章目录 0. 测试效果1. 基本内容1.1 计算点云位姿1.2 调平点云1.3 点云投影2. 代码实现文章目录:3D视觉个人学习目录微信:dhlddxB站: Non-Stop_0. 测试效果

如何远程访问Linux MeterSphere一站式开源持续测试平台

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…

使用 WebGL 为 HTML5 游戏创建逼真的地形

推荐&#xff1a;使用 NSDT场景编辑器快速搭建3D应用场景 建 模 和 3D 地形 大多数 3D 对象是 使用建模工具创建&#xff0c;这是有充分理由的。创建复杂对象 &#xff08;如飞机甚至建筑物&#xff09;很难在代码中完成。建模工具 几乎总是有意义的&#xff0c;但也有例外&am…

深入浅出Android同步屏障机制

原文链接 Android Sync Barrier机制 诡异的假死问题 前段时间&#xff0c;项目上遇到了一个假死问题&#xff0c;随机出现&#xff0c;无固定复现规律&#xff0c;大量频繁随机操作后&#xff0c;便会出现假死&#xff0c;整个应用无法操作&#xff0c;不会响应事件&#xff…

ES6中导入import导出export

ES6使用 export 和 import 来导出、导入模块 用法 /** 导出 export *///分别导出 export let name 孙悟空; export function sum(a, b) {return a b; } } //先定义再导出 let age 18 export {age}/** 默认导出 export default */const a 默认导出; export default a;/**…

手写apply方法

<script>/** 手写apply方法 * */Function.prototype.myApply function (context, args) {console.log(this, sss)//fnconst key Symbol()context[key] thiscontext[key](...args)delete context[key]return context[key]}const obj {name: zs,age: 18}function fn …

$ref属性的介绍与使用

在Vue.js中&#xff0c;$ref是一个特殊的属性&#xff0c;用于访问Vue组件中的DOM元素或子组件实例。它允许你直接访问组件内部的DOM元素或子组件&#xff0c;并且可以在需要时进行操作或修改。以下是有关$ref的详细介绍和示例演示&#xff0c;给大家做一个简单的介绍和概念区分…

Leetcode 1486.数组异或操作

给你两个整数&#xff0c;n 和 start 。 数组 nums 定义为&#xff1a;nums[i] start 2*i&#xff08;下标从 0 开始&#xff09;且 n nums.length 。 请返回 nums 中所有元素按位异或&#xff08;XOR&#xff09;后得到的结果。 示例 1&#xff1a; 输入&#xff1a;n 5, …

YOLOv5:对yolov5n模型进一步剪枝压缩

YOLOv5&#xff1a;对yolov5n模型进一步剪枝压缩 前言前提条件相关介绍具体步骤修改yolov5n.yaml配置文件单通道数据&#xff08;黑白图片&#xff09;修改models/yolo.py文件修改train.py文件 剪枝后模型大小 参考 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;…

小程序中如何查看会员的积分和变更记录

​积分是会员卡的一个重要功能&#xff0c;可以用于激励会员消费和提升用户粘性。在小程序中&#xff0c;商家可以方便地查看会员卡的积分和变更记录&#xff0c;以便更好地了解会员的消费行为和积分变动情况。下面将介绍如何在小程序中查看会员卡的积分和变更记录。 1. 找到指…

Nat. Communications Biology2022 | PepNN+: 用于识别多肽结合位点的深度关注模型

论文标题&#xff1a;PepNN: a deep attention model for the identification of peptide binding sites 论文链接&#xff1a;PepNN: a deep attention model for the identification of peptide binding sites | Communications Biology 代码地址&#xff1a;oabdin / PepN…

【管理运筹学】第 7 章 | 图与网络分析(1,图论背景以及基本概念、术语、矩阵表示)

文章目录 引言一、图与网络的基本知识1.1 图与网络的基本概念1.1.1 图的定义1.1.2 图中相关术语1.1.3 一些特殊图类1.1.4 图的运算 1.2 图的矩阵表示1.2.1 邻接矩阵1.2.2 可达矩阵1.2.3 关联矩阵1.2.4 权矩阵 写在最后 引言 按照正常进度应该学习动态规划了&#xff0c;但我想…

fatal error: -fuse-linker-plugin, but liblto_plugin.so not found 解决方法

参考文章&#xff1a;https://blog.csdn.net/tt_tantao/article/details/91646875 在工具链目录下找到 liblto_plugin.so.0.0.0 复制成一份 liblto_plugin.so 顺利解决

架构师 软件测试

架构师 软件测试 目录概述需求&#xff1a; 设计思路实现思路分析1.软件测试方法 软件测试工具 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for c…

C语言入门 Day_14 for循环

目录​​​​​​​ 1.for循环 2.循环执行顺序 3.易错点 4.思维导图 前言 我们定义了一个数组以后&#xff0c;要使用&#xff08;读取或者修改&#xff09;数组元素的话&#xff0c;可以一个一个的读取&#xff0c;就前两课学的那样&#xff0c;代码类似这个结构。 int …

基于SpringBoot的Web开发案例过程讲解-项目准备

基于SpringBoot的Web开发案例过程笔记-项目准备 1&#xff09;环境搭建【1】准备数据库表【2】创建Springboot项目并引入相关依赖【3】配置application.properties文件【4】创建相关的包和类 2) 三层架构工作流程3&#xff09;开发规范-Restful4&#xff09;相关的注解5)项目开…

MySQL中分区与分表的区别

MySQL中分区与分表的区别 一、分区与分表的区别 分区和分表是在处理大规模数据时的两种技术手段&#xff0c;尽管它们的目标都是提升系统的性能和数据管理的效率&#xff0c;但它们的实现方式和应用场景略有不同。 1. 分区 分区是将一个大表分割为多个更小的子表&#xff0c…