typescript - 从枚举参数推断 Typescript 函数返回类型
问题描述
我想创建一个加载服务,为枚举中定义的 ID 返回正确类型的数据。我所做的看起来像这样:
enum IdentifierEnum {
ID1 = 'ID1',
ID2 = 'ID2'
}
interface DataType {
[IdentifierEnum.ID1]: number,
[IdentifierEnum.ID2]: string
}
class LoadingService {
loadData<K extends IdentifierEnum>(key: K): DataType[K] {
// ...
}
}
使用此方法,在使用加载服务时可以正确推断类型:
const loadingService: LoadingService = new LoadingService();
const data1 = loadingService.loadData(IdentifierEnum.ID1); // type: number
const data2 = loadingService.loadData(IdentifierEnum.ID2); // type: string
我面临的唯一问题是,在 的实现中loadData
,类型参数K
仅被推断为IdentifierEnum
. 因此以下将不起作用:
class LoadingService {
loadData<K extends IdentifierEnum>(key: K): DataType[K] {
if (key === IdentifierEnum.ID1) {
return 1; // Error: Type '1' is not assignable to type 'DataType[K]'.
}
// ..
}
}
这对我来说绝对是有道理的。尽管如此,我还是希望有一个完全类型安全的解决方案。
我已经尝试过重载该函数,但这给我留下了一个问题,即我仍然必须提供一个实现签名,该签名要么过于具体(如上所示),要么过于笼统,这再次消除了我想要的类型安全。转换返回值也是如此。我需要的基本上是一种真正类型检查输入值的方法,而不仅仅是检查它的值。
有可能这样做吗?或者也许是一种完全不同的方法来解决这个问题,为加载服务的使用和实现提供类型安全?
边注
对于这个简单的目的,实现可能看起来过于复杂,但原因是加载服务有一个通用的基类,如下所示:
// Base class
abstract class AbstractLoadingService<E extends string | number | symbol, T extends {[K in E]: any}> {
abstract loadData<K extends E>(key: K): T[K];
}
// Implementation
class LoadingService extends AbstractLoadingService<IdentifierEnum, DataType> {
loadData<K extends IdentifierEnum>(key: K): DataType[K] {
// ...
}
}
解决方案
这是 TypeScript 中的一个已知痛点,请参阅microsoft/TypeScript#13995和microsoft/TypeScript#24085。问题是基于控制流的类型分析不适用于泛型类型参数。
如果key
被声明为 type IdentifierEnum
,则检查会将块内if (key === IdentifierEnum.ID1) {...}
的类型缩小为:key
{...}
Identifier.ID1
const k: IdentifierEnum = key;
if (k === IdentifierEnum.ID1) {
k; // const k: IdentifierEnum.ID1
} else {
k; // const k: IdentifierEnum.ID2
}
那就是控制流分析。现在这不会发生 when key
is 是 generic type K
,但即使发生了,它也不会帮助你:
if (k === IdentifierEnum.ID1) {
return 1; // ERROR!
}
这是因为,无论如何,从 TypeScript 3.8 开始,即使type的值K
被缩小,类型K
本身也不是. 编译器从不说“如果key
是IdentifierEnum.ID1
,那么K
是IdentifierEnum.ID1
”。因此,如果您希望编译器为您验证类型安全,则不能使用这种基于控制流的实现。
未来的 TypeScript 版本可能会在某些方面做得更好,但这很棘手。一般来说,仅仅因为 type 的值x
可以X
缩小为 type Y
,并不意味着类型X
本身可以缩小。如果您有多个类型的值,这很明显X
。但无论如何,就目前而言,这是需要解决的问题。
您已经探索过并且对执行此操作的类型安全性较低的方法感到不满:类型断言和重载签名,因此我将省略写出您将如何实现它。
在这里做一些相对类型安全的事情的唯一方法是放弃控制流分析,而是使用索引操作。编译器足够聪明地意识到,如果你有一个t
类型T
的值和一个k
类型的值K extends keyof T
,那么这个值t[k]
就是类型的T[K]
。在你的情况下,T
是DataType
。所以你需要一个该类型的值来索引:
class LoadingService {
loadData<K extends IdentifierEnum>(key: K): DataType[K] {
return {
get [IdentifierEnum.ID1]() { return 1; },
get [IdentifierEnum.ID2]() { return "" }
}[key]; // okay
}
}
上述类型检查。请注意,我将属性实现为getters。您不必这样做;你可以写:
return {
[IdentifierEnum.ID1]: 1,
[IdentifierEnum.ID2]: ""
}[key];
但是 getter 版本允许您进行更多任意计算,并且知道只有对应的计算key
才会被评估。
推荐阅读
- sql-server - 删除“Sortkey”中的一些“sortkey”后如何对“SortKey”进行sql排序和更新
- c# - Xamarin appshell - 强制立即创建第二个选项卡(视图)
- c# - 实体框架迁移删除和更新不起作用
- python - 你如何在 Django 中使用下载的模板
- wordpress - 如何确保子主题 functions.php 正在运行
- rest - 如果用户想要对其个人资料和个人资料图片执行操作,客户端是否应该为 API 发送两个不同的 HTTP 请求
- excel - 在两个 Excel 实例之间复制值得到运行时错误 9
- java - 如何在 RecyclerView 中获取单击项目的位置
- terraform-provider-azure - 如何使用 Terraform 启用 Application Insights?
- python - Python Selenium:日期选择器选择日期