简体   繁体   English

从枚举参数推断 Typescript 函数返回类型

[英]Infer Typescript function return type from enum parameter

I want to create a loading service that returns properly typed data for IDs defined in an enum.我想创建一个加载服务,为枚举中定义的 ID 返回正确类型的数据。 What I did looks like this:我所做的看起来像这样:

enum IdentifierEnum {
  ID1 = 'ID1', 
  ID2 = 'ID2'
}

interface DataType {
  [IdentifierEnum.ID1]: number,
  [IdentifierEnum.ID2]: string
}

class LoadingService {
  loadData<K extends IdentifierEnum>(key: K): DataType[K] {
    // ...
  }
}

Using this approch, the types are correctly inferred when using the loading service:使用此方法,在使用加载服务时可以正确推断类型:

const loadingService: LoadingService = new LoadingService();
const data1 = loadingService.loadData(IdentifierEnum.ID1); // type: number
const data2 = loadingService.loadData(IdentifierEnum.ID2); // type: string

The only issue I am facing is, that inside the implementation of loadData , the type parameter K is only inferred as IdentifierEnum .我面临的唯一问题是,在loadData的实现中,类型参数K仅被推断为IdentifierEnum Therefor the following won't work:因此,以下将不起作用:

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]'.
    }
    // ..
  }
}

It absolutely makes sense to me that this is the case.对我来说,情况确实如此。 Still, I would love to have a completely typesafe solution for this.尽管如此,我还是希望有一个完全类型安全的解决方案。

I already tried overloading the function, but that leaves me with the problem that still I have to provide an implementing signature, which is either too specific (like the above) or too general, which again removes the typesafety I desire.我已经尝试过重载该函数,但这给我留下了一个问题,即我仍然必须提供一个实现签名,该签名要么太具体(如上述),要么太笼统,这再次消除了我想要的类型安全。 The same goes for casting the return value.转换返回值也是如此。 What I need is basically a way to really type-check the input value instead of just checking its value.我需要的是一种真正对输入值进行类型检查而不是仅仅检查其值的方法。

Is there a possibility to do this?有没有可能做到这一点? Or maybe a whole different way to solve this problem that provides typesafety for both the usage and the implementation of the loading service?或者也许是一种完全不同的方法来解决这个问题,为加载服务的使用和实现提供类型安全?

Side note边注

The implementation may seem overly complex for this simple purpose, but the reason for this is that there is a generic base class for the loading service that looks like this:对于这个简单的目的,实现可能看起来过于复杂,但原因是加载服务有一个通用基类,如下所示:

// 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] {
    // ...
  }
}

This is a known pain point in TypeScript, see microsoft/TypeScript#13995 and microsoft/TypeScript#24085 .这是 TypeScript 中的一个已知痛点,请参阅microsoft/TypeScript#13995microsoft/TypeScript#24085 The issue is that control-flow based type analysis does not apply to generic type parameters.问题是基于控制流的类型分析不适用于泛型类型参数。

If key were declared to be of type IdentifierEnum , then the check if (key === IdentifierEnum.ID1) {...} would narrow the type of key inside the {...} block to be Identifier.ID1 :如果key被声明为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
}
 

That's control flow analysis.这就是控制流分析。 Now this doesn't happen when key is of generic type K , but even if it did, it wouldn't help you:现在,当key是泛型类型K时不会发生这种情况,但即使发生了,它也无济于事:

if (k === IdentifierEnum.ID1) {
  return 1; // ERROR! 
}

That's because, as of TypeScript 3.8 anyway, even if a value of type K is narrowed, the type K itself is not .这是因为,从 TypeScript 3.8 开始,即使类型K变窄,类型K本身也不是 The compiler never says "if key is IdentifierEnum.ID1 , then K is IdentifierEnum.ID1 ."编译器从不说“如果keyIdentifierEnum.ID1 ,则KIdentifierEnum.ID1 So you can't use this sort of control-flow based implementation if you want the compiler to verify type safety for you.因此,如果您希望编译器为您验证类型安全,则不能使用这种基于控制流的实现。

It's possible that future versions of TypeScript will make this better in some way, but it's tricky. TypeScript 的未来版本可能会以某种方式使这更好,但这很棘手。 In general, just because a value x of type X can be narrowed to type Y , it doesn't mean the type X itself can be narrowed.在一般情况下,仅仅因为一个值x型的X可以缩小输入Y ,这并不意味着该型X本身可以缩小。 This is obvious if you have multiple values of type X sitting around.如果您有多个X类型的值,这很明显。 But anyway, for now, this is something to work around.但无论如何,就目前而言,这是需要解决的问题。


You've already explored and been unhappy with the less type-safe ways of doing this: type assertions and overload signatures, so I will dispense with writing out how you would implement that.您已经探索过并且对执行此操作的类型安全性较低的方法感到不满:类型断言和重载签名,因此我将无需写出您将如何实现它。


The only way to do something relatively type safe here is to give up on control flow analysis, and instead use an indexing operation.在这里做一些相对类型安全的事情的唯一方法是放弃控制流分析,而是使用索引操作。 The compiler is smart enough to realize that if you have a value t of type T and a value k of type K extends keyof T , that the value t[k] will be of type T[K] .编译器足够聪明,可以意识到如果您有一个类型为T的值t并且类型为K extends keyof T的值k K extends keyof T ,那么值t[k]的类型将为T[K] In your case, T is DataType .在您的情况下, TDataType So you need a value of that type to index into:所以你需要一个该类型的值来索引:

class LoadingService {
  loadData<K extends IdentifierEnum>(key: K): DataType[K] {
    return {
      get [IdentifierEnum.ID1]() { return 1; },
      get [IdentifierEnum.ID2]() { return "" }
    }[key]; // okay
  }
}

The above type checks.上述类型检查。 Note that I implemented the properties as getters .请注意,我将属性实现为getter You don't have to do this;你不必这样做; you could have just written:你可以这样写:

return {
  [IdentifierEnum.ID1]: 1,
  [IdentifierEnum.ID2]: ""
}[key];

but the getter version allows you to do more arbitrary calculations and know that only the one corresponding to key will get evaluated.但是 getter 版本允许您进行更多的任意计算,并且知道只有与key对应的才会被评估。


Playground link to code Playground 链接到代码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM