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嵌套时的返回值逐级穿透问题
相关文章:
Vue3【Provide/Inject】
前言 自从使用了Provide/Inject代码的组织方式更加灵活了,但是这个灵活性的增加伴随着代码容错性的降低。我相信只要是真的在项目中引入Provide/Inject的同学,一定一定有过或者正在经历下面的状况: 注入名(Injection key&#x…...
Go-Python-Java-C-LeetCode高分解法-第四周合集
前言 本题解Go语言部分基于 LeetCode-Go 其他部分基于本人实践学习 个人题解GitHub连接:LeetCode-Go-Python-Java-C Go-Python-Java-C-LeetCode高分解法-第一周合集 Go-Python-Java-C-LeetCode高分解法-第二周合集 Go-Python-Java-C-LeetCode高分解法-第三周合集 本…...
vue路由
一、声明式导航-导航链接 1.需求 实现导航高亮效果 如果使用a标签进行跳转的话,需要给当前跳转的导航加样式,同时要移除上一个a标签的样式,太麻烦!!! 2.解决方案 vue-router 提供了一个全局组件 router…...
最强的AI视频去码图片修复模型:CodeFormer
目录 1 CodeFormer介绍 1.1 CodeFormer解决的问题 1.2 人脸复原的挑战 1.3 方法动机 1.4 模型实现 1.5 实验结果 2 CodeFormer部署与运行 2.1 conda环境安装 2.2 运行环境构建 2.3 模型下载 2.4 运行 2.4.1 人脸复原 编辑编辑 2.4.2 全图片增强 2.4.3 人脸颜色…...
jenkins自动化部署安装
一、准备工作 1、安装jdk # 1、下载准备jdk包(也可以用docker安装) wget ... # 2、直接解压到,无需安装 unzip ...2、安装maven # 1、下载准备maven压缩包 wget ... # 2、直接解压,无需安装 unzip ... # 3、修改setting.xml,修改localRepository和MIRROR镜像地址…...
如何调用Zabbix API获取主机信息
自Zabbix 1.8版本被引进以后,Zabbix API开始扮演着越来越重要的角色,它可以为批量操作、第三方软件集成以及其他应用提供可编程接口。 在运维实践中,Zabbix API还有更多巧妙的应用。 面对规模庞大的监控设备,可能会出现某台机器发…...
批量执行redis命令总结
目录 批量执行redis命令方式1: redis-cli直接执行方式2:通过redis-cli和xargs等命令 批量执行redis命令 方式1: redis-cli直接执行 redis-cli command param redis-cli本身支持单个命令执行省略了连接参数操作的key等相关数据,可以通过线下获取或通过keys scan等命…...
命令行git联网失败,但是实际可以联网
最近下载代码的时候发现总是告诉我连不上github的网页,但是我自己通过浏览器又可以上网,找了半天发现这个方法可以。 记录下这个代理 打开git bash 执行以下命令: git config --global http.proxy http://127.0.0.1:7890 git config --glob…...
网络编程套接字,Linux下实现echo服务器和客户端
目录 1、一些网络中的名词 1.1 IP地址 1.2 端口号port 1.3 "端口号" 和 "进程ID" 1.4 初始TCP协议 1.5 UDP协议 2、socket编程接口 2.1 socket 常见API 2.2 sockaddr结构 3、简单的网络程序 3.1 udp实现echo服务器和客户端 3.1.1 echo服务器实…...
java+ssh+mysql智能化办公管理系统
项目介绍: 本系统为基于jspsshmysql的OA智能办公管理系统,包含管理员、领导、员工角色,功能如下: 管理员:公告信息;工作计划;公司资料;部门管理;员工管理;员…...
网络层抓包tcpdump
sudo tcpdump -i eth0 -s 0 -nn host iphost -w xxx.pcap 这段代码使用了命令行工具 tcpdump,用于在Linux系统上捕获网络数据包。让我详细介绍一下这段代码的含义和 tcpdump 的用法: 代码含义: sudo: 使用超级用户权限执行 tcpdump 命令&am…...
QT之形态学操作
形态学操作包含以下操作: 腐蚀 (Erosion)膨胀 (Dilation)开运算 (Opening)闭运算 (Closing)形态梯度 (Morphological Gradient)顶帽 (Top Hat)黑帽(Black Hat) 其中腐蚀和膨胀操作是最基本的操作,其他操作由这两个操作变换而来。 腐蚀 用一个结构元素…...
15、监测数据采集物联网应用开发步骤(11)
源码将于最后一遍文章给出下载 监测数据采集物联网应用开发步骤(10) 程序自动更新开发 前面章节写了部分功能模块开发: 日志或文本文件读写开发;Sqlite3数据库读写操作开发;定时器插件化开发;串口(COM)通讯开发;TCP/IP Client开发;TCP/IP Server 开发;modbus协议…...
Pygame中Trivia游戏解析6-2
3.1.2 读取保存题目的文件 在Trivia类的__init__()方法中,对各变量初始化完成之后,读取保存题目的文件,代码如下所示。 f open(filename, "r", encodingutf8) trivia_data f.readlines() f.close() 其中,open()函数…...
java 实现命令行模式
命令模式是一种行为设计模式,它允许您将请求封装为对象,以便您可以将其参数化、队列化、记录和撤销。在 Java 中实现命令模式涉及创建一个命令接口,具体命令类,以及一个接收者类,该接收者类执行实际操作。下面是一个简…...
A - Orac and Models(最长上升子序列——加强版)
There are nn models in the shop numbered from 11 to nn, with sizes s_1, s_2, \ldots, s_ns1,s2,…,sn. Orac will buy some of the models and will arrange them in the order of increasing numbers (i.e. indices, but not sizes). Orac thinks that the obtai…...
【python手写算法】逻辑回归实现分类(含公式推导)
公式推导: 代码实现: # codingutf-8 import matplotlib.pyplot as plt import numpy as npdef f(w1,x1,w2,x2,b):zw1*x1w2*x2breturn 1/(1np.exp(-z)) if __name__ __main__:X1 [12.46, 0.25, 5.22, 11.3, 6.81, 4.59, 0.66, 14.53, 15.49, 14.43,2.1…...
【2023高教社杯数学建模国赛】ABCD题 问题分析、模型建立、参考文献及实现代码
【2023高教社杯数学建模国赛】ABCD题 问题分析、模型建立、参考文献及实现代码 1 比赛时间 北京时间:2023年9月7日 18:00-2023年9月10日20:00 2 思路内容 可以参考我提供的历史竞赛信息内容,最新更新我会发布在博客和知乎上,请关注我获得最…...
yum安装mysql5.7散记
## 数据源安装 $ yum -y install wget $ wget http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm $ yum localinstall mysql57-community-release-el7-8.noarch.rpm $ yum repolist enabled | grep "mysql.*-community.*" $ yum install mysql-…...
DNS解析
1.DNS介绍 DNS 表示域名系统。此系统实质上是用于整理和识别各个域名的网络电话簿。电话簿将“Acme Pizza”之类的名称转换为要拨打的正确电话号码,而 DNS 将“www.google.com”之类的网络地址转换为托管该网站的计算机的物理 IP 地址,如“74.125.19.147…...
从jdk8 升级到jdk17的问题总结
目录 1. java.lang.reflect.InaccessibleObjectException: 2. java.lang.UnsatisfiedLinkError in autosys 3. java.lang.NoClassDefFoundError: Could not initialize class net.sf.jasperreports.engine.util.JRStyledTextParser 4. java.lang.UnsatisfiedLinkError: **…...
一百七十二、Flume——Flume采集Kafka数据写入HDFS中(亲测有效、附截图)
一、目的 作为日志采集工具Flume,它在项目中最常见的就是采集Kafka中的数据然后写入HDFS或者HBase中,这里就是用flume采集Kafka的数据导入HDFS中 二、各工具版本 (一)Kafka kafka_2.13-3.0.0.tgz (二)…...
pnpm 升级
1. 在以下路径下删除pnpm包 2. 执行which pnpm,在结果目录中删除pnpm 3. sudo npm install -g pnpm 重新安装,node默认使用16...
有关使用HttpServletRequest的Cookie的设置和获取
文章目录 小结问题和解决参考 小结 介绍了如何在HttpServletRequest中对Cookie的进行设置和获取。 问题和解决 在服务器端的HttpServletRequest中对Cookie的进行设置后,客户端在接下来的请求中会携带此设置好的Cookie,所以可以在服务器端接收请求时提…...
关于 Nginx 的哪些事
关于 Nginx 的哪些事 1、Nginx 主要功能2、Nginx 的常用命令2.1、启动Nginx2.2、停止 Nginx2.3、重新加载Nginx 配置2.4、检查Nginx配置文件2.5、指定配置文件2.6、检查Nginx版本2.7、显示Nginx帮助信息 3、Nginx 配置文件 nginx.conf3.1、Nginx 配置文件(nginx.con…...
插入排序——希尔排序
1、简述: 希尔排序(Shells Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。 希尔排…...
C语言之初阶总结篇
目录 NO.1 NO.2 NO.3 NO.4 NO.5 NO.6 NO.7 NO.8 NO.9 NO.10 NO.11 NO.12.概念tips NO.13.求最小公倍数 NO.14.最大公因数 NO.15.输入读取字符串 NO.16.倒置字符串 今天是一些C语言题目,最近天气炎热,多喝水。 NO.1 下面程序执行后&am…...
Android签名查看
查看签名文件信息 第一种方法: 1.打开cmd,执行keytool -list -v -keystore xxx.keystore,效果如下图: 第二种方法: 1.打开cmd,执行 keytool -list -v -keystore xxxx.keystore -storepass 签名文件密码࿰…...
Educational Codeforces Round 3
目录 A. USB Flash Drives B. The Best Gift C. Load Balancing D. Gadgets for dollars and pounds A. USB Flash Drives #include<bits/stdc.h>using namespace std; const int N1e65; typedef long long ll; typedef pair<ll,ll> pll; typedef array<int…...
Docker Compose常用命令
常用命令 1.1 restart, start, stop-- 启动和停止服务 命令必须在 docker-compose.yml文件所在的目录下执行。 # 前台启动, 启动项目中的所有服务。 $. docker-compose up# 后台启动, 启动所有服务并在后台运行。 $. docker-compose up -d# 停止所有服务。 $. docker-compose …...
建设网站电脑配置/十大计算机培训学校
讲一个项目常见的功能,友盟统计功能 例如一个项目有很多多modlue,每个里面modlue都有Activity,Activity需要友盟统一,Fragment也需要友盟统计。一般做法就是继承一个BaseActivity,BaseFragment。 然后在BaseActivity,BaseFragment大概是这样的…...
wordpress更换新主题/国内真正的免费建站
在实际引用的场景中,经常会有这样一种情况,客户那边由于网络或者安全考虑需要内网操作,没有对应的网络环境,进行项目创建和引用添加都只能通过离线的方式添加,那么如何离线安装AR对应的引用? 点击获取Acti…...
闵行区做网站/淘宝怎么设置关键词搜索
一.Core标签库 • 核心标签库主要包括通用标签、条件标签、迭代标签和与URL相关的标签。 • 在使用Core标签库的JSP文件的开始部分,添加代码: <%taglib uri"http://java.sun.com/jsp/jstl/core" prefix"c"…...
wordpress 显示pdf/人工智能培训机构哪个好
上一期与大家探讨了做自媒体需要硬件与软件,今天接着和大家探讨做自媒体的心得二,做自媒体如何赚钱盈利,做自媒体该怎么样去做?从哪方面去做等问题。 怎么做自媒体,做自媒体如何赚钱盈利 一、是做图文自媒体 图文就是…...
营销型网站怎么收费标准/设计网络推广方案
正确的使用键盘上的符号可以在帮助你在编写简单脚本时充分应用各种技巧[Tab] 用于自动补齐一个命令和路径或文件名[rootlocalhost ~]# user 在输入user 后连按键盘上的[Tab]键两下,可以将以user开头的命令列出[rootlocalhost ~]# usera 在输入usera后连按键盘上的[T…...
国内vps做网站备案/浏览器打开网站
文件管理:文件的物理结构1.文件的物理结构1.1 文件分配方式1.1.1 连续分配1.1.2 链接分配1.1.2.1 隐式链接1.1.2.2 显式链接1.1.3 索引分配1.文件的物理结构 文件系统层次结构 文件物理结构位于第五层 下图来自王道考研操作系统 逻辑结构:在用户看来&…...