我想创建一个加载服务,该服务返回枚举中定义的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] {
// ...
}
}
答案 0 :(得分:1)
这是TypeScript中的已知痛点,请参见microsoft/TypeScript#13995。问题是control-flow based type analysis不适用于通用类型参数。
如果将key
声明为IdentifierEnum
类型,则检查if (key === IdentifierEnum.ID1) {...}
会将key
块内{...}
的类型缩小为{ {1}}:
Identifier.ID1
那是控制流分析。现在,const k: IdentifierEnum = key;
if (k === IdentifierEnum.ID1) {
k; // const k: IdentifierEnum.ID1
} else {
k; // const k: IdentifierEnum.ID2
}
的类型为key
时不会发生这种情况,但是即使发生了,也无济于事:
K
这是因为从TypeScript 3.8开始,即使if (k === IdentifierEnum.ID1) {
return 1; // ERROR!
}
类型的 value 变窄,类型K
本身也是 not 。编译器永远不会说“如果K
是key
,那么IdentifierEnum.ID1
是K
”。因此,如果您希望编译器为您验证类型安全,则不能使用这种基于控制流的实现。
将来的TypeScript版本有可能以某种方式使它变得更好,但这很棘手。通常,仅可以将类型IdentifierEnum.ID1
的值x
缩小为类型X
,这并不意味着类型Y
本身可以缩小。如果您有多个X
类型的值,这很明显。但是无论如何,目前来说,这是可以解决的。
您已经探索了这种类型安全性较差的方法,并且对此类型感到不满意:类型声明和重载签名,因此我将省去写出如何实现的方法。
在这里做相对安全的事情的唯一方法是放弃控制流分析,而使用索引操作。编译器足够聪明,可以意识到,如果您拥有类型X
的值t
和类型T
的值k
,则值K extends keyof T
将类型为t[k]
。在您的情况下,T[K]
是T
。因此,您需要该类型的值才能索引到
DataType
以上类型检查。请注意,我将属性实现为getters。您不必这样做;您可能刚刚写过:
class LoadingService {
loadData<K extends IdentifierEnum>(key: K): DataType[K] {
return {
get [IdentifierEnum.ID1]() { return 1; },
get [IdentifierEnum.ID2]() { return "" }
}[key]; // okay
}
}
但是使用getter版本,您可以进行更多的任意计算,并且知道只有与return {
[IdentifierEnum.ID1]: 1,
[IdentifierEnum.ID2]: ""
}[key];
相对应的计算才会被评估。
好的,希望能有所帮助;祝你好运!