Vue3 + TS 中使用 Provide/Inject 需要考虑的三大问题

Provide/Inject的作用

在组件通信的场景中,必然会遇到跨层级组件间传值的问题,尤其是爷------孙组件,甚至是更深层级的组件。比如下图中,App.vue 文件将属性 name 一层层地往下传给组件 form-item.vue:

对于这种类型的组件传值是有解决方法的,那就是使用 props 接受属性后,再不断的往下传,但是这种方式会非常麻烦,写很多不必要的代码;第二种方式是把要传递的属性放到全局状态管理当中,所有组件都能共享,但并不是适用所有的场景,有些单独的属性我们并不想放到全局状态管理器里面。

这个时候,Provide/Inject 的出现就是很好的解决深层组件间传值的问题,一句话概括 Provide/Inject 的作用:实现 Prop 逐级透传。

Provide/Inject 的简单使用:

js 复制代码
// App.vue
import { provide } from 'vue';
provide('name', 'zhangsan');

// form-item.vue
import { inject } from 'vue';
const name = inject('name');

在 Vue3 + TS 的项目中使用 Provide/Inject 其实是很简单的,但是如果我们想要用好这个 API,其实需要考虑到很多东西,尤其是在复杂的项目里面,那么这篇文章将给大家分享 使用 Provide/Inject 需要解决的三大问题。

问题一:命名冲突

在项目业务逻辑十分复杂,多人协作开发的情况下,很容易出现 provide 提供的 key 值发生冲突的问题,比如下面的场景:

title.vue 组件无法得知 name 属性是由哪个组件提供的,当然解决方法是非常多的,直接改个名称就行了,最简单的方式,但是复杂项目就不好处理了,而且可能其他同事在某个组件中 provide 的名称与我们的一样,还得麻烦自己去查找名称是否有冲突。

倒不如我们在命名的时候,取个唯一的名称,在 JS 里面 Symbol 类型的数据便可以帮我们解决这个问题。为了便于项目的规范,新建 inject-keys.ts 文件专门存放注入的名称:

js 复制代码
// src/inject-keys.ts
export const HomeNameKey = Symbol('name');
export const OtherNameKey = Symbol('name');

使用如下:

js 复制代码
// Home.vue
import { provide } from 'vue'; 
import { HomeNameKey } from '@/constants/inject-keys'; 
provide(HomeNameKey, 'hahaha');

// title.vue
import { inject } from 'vue';
import { HomeNameKey } from '@/constants/inject-keys';
const name = inject(HomeNameKey);

问题二:类型提示

对于 inject(HomeNameKey) 会得到一个 name 变量,这个时候如果我们想要知道 name 的类型是啥,就需要找到 provide(HomeNameKey, 'hahaha'),这样其实会比较麻烦,我们既然都使用 TS 进行项目的开发了,为什么不指定其类型呢?在组件中使用时自动获取数据的类型提示。

Vue3 中提供了 InjectionKey 用于定义注入变量的类型:

ts 复制代码
import { InjectionKey } from 'vue';

export type infoVO = {
    name: string;
    age: number;
}

export const InfoKey: InjectionKey<infoVO> = Symbol('info');

在组件中使用:

js 复制代码
// Home.vue
import { provide } from 'vue'; 
import { InfoKey } from '@/constants/inject-keys'; 

provide(InfoKey, 'zhangsan');  // 错误
provide(InfoKey, {name: 'hahah', age: 18});  // 正确

问题三:严格注入

在使用 inject 时会遇到一个问题,那就是如果注入的名称 InfoKey 在其祖先组件中并没有提供,那么 inject(InfoKey) 是会出现问题的,其实可以使用默认值来解决祖先组件未提供的情况,inject 这个 API 是可以接受三个参数的:

  • 第一个参数是注入的 key

  • 第二个参数是可选的,即在没有匹配到 key 时使用的默认值。

  • 第二个参数也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。在这种情况下,你必须将 true 作为第三个参数传入,表明这个函数将作为工厂函数使用,而非值本身。

js 复制代码
// 注入一个值,若为空则使用提供的默认值 
const bar = inject('path', '/default-path') 
// 注入一个值,若为空则使用提供的函数类型的默认值 
const fn = inject('function', () => {}) 
// 注入一个值,若为空则使用提供的工厂函数 
const baz = inject('factory', () => new ExpensiveObject(), true)

但是有些情况下要求祖先链上必须提供需要的内容,尤其是在一些通用型组件的开发中,这个时候应该抛出错误而不是警告,因此需要解决这个问题。封装一个工具函数:

js 复制代码
export const injectStrict = <T>(key: InjectionKey<T>, defaultValue?: T | (() => T), treatDefaultAsFactory?: false): T => {
  const result = inject(key, defaultValue, treatDefaultAsFactory); 
  if (!result) { 
    throw new Error('xxxxxxxxxxxxx'); 
  } 
  return result;
}

使用:

js 复制代码
import { inject } from 'vue';
import { HomeNameKey } from '@/constants/inject-keys';
const name = injectStrict(HomeNameKey);
相关推荐
GISer_Jing3 小时前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪3 小时前
CSS复习
前端·css
咖啡の猫5 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲8 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5818 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路8 小时前
GeoTools 读取影像元数据
前端
ssshooter9 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry10 小时前
Jetpack Compose 中的状态
前端
dae bal10 小时前
关于RSA和AES加密
前端·vue.js
柳杉10 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化