Provide/Inject + TypeScript 使用
作者:许多金
翻译来自: 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! 👋
- 点赞
- 收藏
- 关注作者
评论(0)