Vue3【Provide/Inject】

news2025/2/23 4:22:31

前言

自从使用了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.coloradmin.cn/o/982652.html

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

相关文章

第二证券:经济利好,联储“利空”

当地时刻9月6日&#xff0c;美股接连9月低迷局面&#xff0c;三大指数低开低走团体收跌。 宏观经济数据方面&#xff0c;美国8月ISM非制造业PMI意外升至半年新高&#xff0c;接连八个月扩张&#xff0c;工作指数升至2021年11月来最高&#xff0c;新订单指数也创六个月新高&…

NATAPP内网穿透之接口测试

下载 工具下载 下载对应系统版本的软件&#xff1a; NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 认证文件下载 使用本地配置文件config.ini - NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 解压完之后&#xff0c;将配置文件放到解压后的文件夹中 申请免费隧…

[Linux]文件系统

[Linux]文件系统 文件系统是操作系统的一部分&#xff0c;负责组织、存储和管理存储在外部设备上的文件和目录&#xff0c;也就是操作系统管理外设中的文件的策略。本文讲解的是Ext2文件系统。Linux操作系统使用的就是Ext系列的文件系统。 文章目录 [Linux]文件系统了解磁盘结构…

集美大学计算机改考408!福建省全面改考,仅剩一个自命题院校

9月5日&#xff0c;集美大学发布通知&#xff0c;0835软件工程、0854电子信息2024考试科目发生变更&#xff01;由822数据结构调整为408计算机学科专业基础 https://zsb.jmu.edu.cn/info/1532/4701.htm 直接由一门改为考四门&#xff0c;难度升级不小。 目前福建省内计算机考…

23年下半年软考系统集成(中项)报名已开始!

2023下半年软考考试报名今日播报如下&#xff1a; 【新增报名地区】宁夏 【正在报名地区】辽宁、江西、新疆、新疆兵团、内蒙古、河北、西藏、山东、云南、河南、河北、西藏、山东、云南、河南、江苏、黑龙江、大连、广东、海南、四川、宁夏 为什么现在报名系统集成项目管理工程…

redis 配置与优化

目录 一、关系数据库和非关系型数据库 二、关系型数据库和非关系型数据库区别 三、非关系型数据库产生背景 四、redis 1、概念 2、redis的优点 3、redis为什么这么快 五、redis安装与配置 一、关系数据库和非关系型数据库 关系型数据库&#xff1a;关系型数据库是一个结…

防雷检测的作用和意义

防雷检测是指对建筑物的雷电防护装置进行检测的活动&#xff0c;其目的是确定防雷装置是否符合国家标准或行业标准、设计文件的要求&#xff0c;查出事故隐患&#xff0c;防止或减少雷电灾害事故的发生。防雷检测是防雷工程的重要组成部分&#xff0c;也是保障建筑物安全运行的…

作为产品经理,有必要考PMP或者NPDP么?

产品经理的核心竞争力是什么? 三点&#xff1a;知识、能力和决策 懂得越多&#xff0c;能力越强&#xff0c;决策越正确&#xff0c;核心竞争力越强。一般来说&#xff0c;看的越多&#xff0c;做的越多&#xff0c;实践出经验才是王道&#xff0c;但是&#xff0c;总有看不…

postgresql数据库定时备份到远程数据库

postgresql数据库定时备份到远程数据库 1.老规矩&#xff0c;服务器目录结构: conf目录无内容 profile: # /etc/profile: system-wide .profile file for the Bourne shell (sh(1)) # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).if [ "id -u"…

uView实现全屏选项卡

// 直接复制粘贴即可使用 <template><view><view class"tabsBox"><u-tabs-swiper ref"uTabs" :list"list":current"current"change"tabsChange":is-scroll"false"></u-tabs-swiper&g…

录音工具哪个好用?亲身测评,推荐这几个

“电脑录音用什么工具呀&#xff0c;前几天录制的视频声音没有录进去&#xff0c;现在需要重新补录声音&#xff0c;但是找不到合适的录音工具&#xff0c;就想问问大家&#xff0c;有没有好用的录音工具推荐呀&#xff1f;” 现如今&#xff0c;录音工具在我们的生活和工作中…

ant-table组件表格数据做合计行,并固定在表格底部

需求描述 某些时候&#xff0c;我们表格展示数值型数据的时候&#xff0c;可能会想做一个合计处理&#xff0c;这样能直观的了解到当前列的总计数据信息。 遍观Table组件&#xff0c;官方是没有提供这个功能的&#xff0c;这就需要我们自己处理了 需求分析 根据表格的特性&a…

链表逆置题

本题要求实现一个函数&#xff0c;将给定单向链表逆置&#xff0c;即表头置为表尾&#xff0c;表尾置为表头。链表结点定义如下&#xff1a; struct ListNode { int data; struct ListNode *next; }; 函数接口定义&#xff1a; struct ListNode *reverse( struct ListNode *hea…

TOWE雷达光敏感应开关,让生活更智能、更安全

现代生活中&#xff0c;智能家居成为人们追求品质生活的必备之选。其中&#xff0c;照明控制的智能化已然成为一种趋势&#xff0c;传统的灯光开关需要人们手动操作&#xff0c;既不方便&#xff0c;有时候也会造成资源的过度浪费&#xff0c;而雷达光敏感应开关的出现&#xf…

数据库中了malloxx勒索病毒了怎么办?勒索病毒解密

Malloxx是一种新兴的勒索病毒&#xff0c;它通常通过攻击计算机系统中的文件和数据来进行恶意加密和锁定。这种病毒已经引起了广泛的关注&#xff0c;因为它采用了先进的技术手段和策略&#xff0c;使得受害者很难自行解决问题。 Malloxx勒索病毒的攻击方式非常狡猾&#xff0c…

ClickHouse进阶(十):Clickhouse数据查询-4

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术,IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &#x1f4cc;订阅…

振弦采集仪应用地铁隧道安全监测详细解决方案

振弦采集仪应用地铁隧道安全监测详细解决方案 随着城市化进程的不断加快&#xff0c;地铁作为一种高效、便捷、环保的交通方式已经成为现代城市不可或缺的一部分。因此&#xff0c;对地铁的安全性也越来越重视&#xff0c;一般二三线以上的城市在不断发展中&#xff0c;地铁做…

Nginx 部署离线瓦片服务

nginx 部署离线瓦片服务&#xff0c;地图资源加载 一、下载离线瓦片数据&#xff0c;部署在本地二、nginx.conf 配置文件三、浏览器中访问 上一篇&#xff1a; nginx 安装与部署 一、下载离线瓦片数据&#xff0c;部署在本地 二、nginx.conf 配置文件 #user nobody; worker_…

从C语言到C++_38(C++的IO流+空间适配器)STL六大组件联系

目录 1. C语言的输入和输出和流 2. C的IO流 2.1 C标准IO流 2.2 C文件IO流 2.3 stringstream&#xff08;字符流&#xff09; 3. 空间配置器&#xff08;了解&#xff09; 3.1 一级空间适配器 3.2 二级空间配置器 3.3 二级空间适配器的空间申请 3.4 二级空间配置器的空…

企业架构LNMP学习笔记16

基于IP的访问控制&#xff1a; 基于ngx_http_access_module模块&#xff0c;默认可使用。 语法是&#xff1a; deny ip 禁止IP访问 allow ip 允许IP访问 上面是允许的&#xff0c;下面是deny的。 老师建议写在server段中是比较合适的。 基于用户的访问控制&#xff1a; …