Provide/Inject + TypeScript 使用

举报
yd_244540595 发表于 2024/07/15 18:18:41 2024/07/15
【摘要】 作者:许多金翻译来自: logaretm.com/blog/2020-1…顺便吆喝一声,技术大厂,内推捞人,前 / 后端 or 测试←感兴趣要求学历:全日制统招本科(非学院派即可):-- 加班偶尔较多,但周末加班两倍工资。--15-35K,工资在一线城市属于一般,但二线城市很可以。 本文是一篇关于 provide/inject TypeScript 用法介绍的简短文章,在 Vue3 以及 V...

作者:许多金

翻译来自: logaretm.com/blog/2020-1…

顺便吆喝一声,技术大厂,内推捞人,前 / 后端 or 测试←感兴趣
要求学历:全日制统招本科(非学院派即可):
-- 加班偶尔较多,但周末加班两倍工资。
--15-35K,工资在一线城市属于一般,但二线城市很可以。

 

本文是一篇关于 provide/inject TypeScript 用法介绍的简短文章,在 Vue3 以及 Vue 2 的 @vue/composition-api 都支持 provide/inject TypeScript 用法。

Provide 类型安全

刚开始在组合 API 中使用 provide/inject 的时候,我写的代码如下:

import { inject } from 'vue';
import { Product } from '@/types';
export default { 
  setup() { 
    const product = inject('product') as Product; 
  },
};

这样写,你发现问题了吗?

在使用 TypeScript 的时候,如果不知道怎么让 TypeScript 理解正在处理的类型,我会使用 as 关键词作为了一种逃避手段。即使我很少使用 as, 但是我仍然尽量避免去使用 as。

不久前,关于这个话题我还发布了 tweeter:我在一个 TypeScript 产品应用中使用了组合 API,需要为 provide/inject 提供类型信息,我打算自己来做,但发现 Vue 已经有一个名为 InjectionKey<T> 的工具类型,它正是我需要的。


这意味着你需要有一个特殊的常量文件来保存 Injectable 键,然后你可以使用 InjectionKey<T> 来创建包含注入属性类型信息的 Symbol

// types.ts
interface Product { name: string; price: number;}

// symbols.ts
import { InjectionKey } from 'vue';
import { Product } from '@/types';
const ProductKey: InjectionKey<Product> = Symbol('Product');

InjectionKey<T> 的优点在于它可以双向工作。它提供了类型安全来提供,这意味着如果你试图用那个键提供一个不兼容的值,TypeScript 会报错:

import { provide } from 'vue';
import { ProductKey } from '@/symbols';
// ⛔️ Argument of type 'string' is not assignable to ...
provide(ProductKey, 'this will not work');
// ✅
provide(ProductKey, {
  name: 'Amazing T-Shirt',
  price: 100,
});
 

在接收端,你的 inject 也会被正确输入:

import { inject } from 'vue';
import { ProductKey } from '@/symbols';
const product = inject(ProductKey); // typed as Product or undefined

需要注意的一点是,inject 函数将生成与 undefined 结合的解析类型。这是因为有可能组件并没有注入。这取决于你想如何处理它。

要消除 undefined,需要向 inject 函数传递一个默认值。这里很酷的是,默认值也是类型检查的:

import { inject } from 'vue';
import { ProductKey } from '@/symbols';
// ⛔️ Argument of type 'string' is not assignable to ...
const product = inject(ProductKey, 'nope');
// ✅ Type checks out
const product = inject(ProductKey, { name: '', price: 0 });

Provide 响应式的值

虽然你可以 provide 普通的值类型,但它们并不常用,因为我们往往需要对这些值的更改作出反应。还可以使用泛型类型创建 reactive 注入。

对于使用 ref 创建的响应式引用,你可以使用泛型 Ref 类型来输入你的 InjectionKey,所以它是一个嵌套泛型类型:

// types.ts
interface Product {
  name: string;
  price: number;
}
// symbols.ts
import { InjectionKey, Ref } from 'vue';
import { Product } from '@/types';
const ProductKey: InjectionKey<Ref<Product>> = Symbol('Product');

现在,当你通过 ProductKey 获取组件 inject 内容时,将得到具有 Ref 类型或 undefined,就像我们之前讨论的那样:

import { inject } from 'vue';
import { ProductKey } from '@/symbols';
const product = inject(ProductKey); // typed as Ref<Product> | undefined
product?.value; // typed as Product

处理 undefined

我已经提到了如何处理用普通值解析 undefined 的问题,但是对于复杂对象和响应性对象,你无法提供一个安全的默认值。

在我们的示例中,我们尝试解决一个 Product 上下文对象,如果该注入不存在,那么可能存在一个更严重的潜在问题,如果没有找到注入,可能会出现错误。

Vue 默认显示一个警告如果不解决注射,Vue 可以选择抛出错误如果没有找到注射但 Vue 不能假设是否需要注射,这是由你来理解解决注入和 undefined 的值。

对于可选的注入属性,只要提供一个默认值,或者如果不是那么重要,你也可以使用可选的链接操作符:

import { inject } from 'vue';
import { CurrencyKey } from '@/symbols';
const currency = inject(CurrencyKey, ref('$'));
currency.value; // no undefined
// or
const currency = inject(CurrencyKey);
currency?.value;

但是对于像我们的 Product 类型这样的需求,我们可以做一些像这样简单的事情:

import { inject } from 'vue';
import { ProductKey } from '@/symbols';
const product = inject(ProductKey);
if (!product) {
  throw new Error(`Could not resolve ${ProductKey.description}`);
}
product.value; // typed as `Ref<Product>`

抛出错误是利用 TypeScript 类型检查特性的一种方法。因为我们在早期处理了 undefined 的组件,所以如果实际使用的时候不添加 product 类型 ,代码就无法正常运行到最后一行。

为了更可重用,让我们创建一个名为 injectStrict 的函数,它为我们做所有这些:

function injectStrict<T>(key: InjectionKey<T>, fallback?: T) {
  const resolved = inject(key, fallback);
  if (!resolved) {
    throw new Error(`Could not resolve ${key.description}`);
  }
  return resolved;
}

现在,你可以直接使用它而不是 inject,你将以模块化的方式获得同样的安全性,而不必处理讨厌的 undefined:

import { injectStrict } from '@/utils';

const product = injectStrict(ProductKey);

product.value; // typed as `Product`

总结

我认为 provide/inject 会越来越流行,特别是随着 composition API 的出现,了解它们的 TypeScript 功能会让你的代码更容易维护,使用起来也更安全。

Thanks for reading! 👋

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。