简体   繁体   English

类型推断如何与 TypeScript 中的联合类型(+条件类型和泛型)一起使用?

[英]How does type inference work with union types (+conditional types and generics) in TypeScript?

I am developing a game in Angular, and trying to decouple presentation from game logic.我正在用 Angular 开发一个游戏,并试图将演示与游戏逻辑分离。 In order to achieve this I have built a separate UiController service to handle user interactions and presentation.为了实现这一点,我构建了一个单独的UiController服务来处理用户交互和演示。 Services related to game logic make requests to UiController whenever something needs to be shown or a user action is needed.每当需要显示某些内容或需要用户操作时,与游戏逻辑相关的服务都会向UiController发出请求。

In order to achieve this as neatly as possible, I'm trying to abstract away interfaces to interact with UiController .为了尽可能巧妙地实现这一点,我试图抽象出接口以与UiController交互。 One common interaction is choice , used when players must choose one among different options of the same category.一种常见的交互是选择,当玩家必须在同一类别的不同选项中选择一个时使用。 That interaction is handled by a requestChoice() method of UiController , which requires a parameter of ChoiceRequest type.该交互由UiControllerrequestChoice()方法处理,该方法需要ChoiceRequest类型的参数。 Since there are many different categories for choices, this type must contain all of them and the method must know how to deal with all of them.由于有许多不同的类别可供选择,因此该类型必须包含所有这些类别,并且该方法必须知道如何处理所有这些类别。

For instance, users may be required to choose monsters or heroes.例如,用户可能需要选择怪物或英雄。 I use literal types to refer to options in choices:我使用文字类型来指代选择中的选项:

type HeroType = 'warrior' | 'rogue' | 'mage';
type MonsterType = 'goblin' | 'demon' | 'dragon';

The first approach that occured to me to build ChoiceRequest was to use generics and conditional types:ChoiceRequest第一种构建ChoiceRequest是使用泛型和条件类型:

type ChoiceType = 'hero' | 'monster';

type OptionsSet<T extends ChoiceType> = T extends 'hero'
  ? HeroType[]
  : T extends 'monster'
  ? MonsterType[]
  : never;

interface ChoiceRequest<T extends ChoiceType> {
  player: Player;
  type: T;
  options: OptionsSet<T>;
}

This proved useful when building choice requests like this, since the values for type and items in options are correctly predicted or rejected:这在构建这样的选择请求时被证明是有用的,因为optionstype和项目的值被正确预测或拒绝:

const request: ChoiceRequest<'monster'> = {
  player: player2,
  type: 'monster',              // OK, any other value wrong
  options: ['demon', 'goblin']  // OK, any value not included in MonsterType wrong.
}

However, type inference does not work as expected when I try to make requestChoice() method handle different cases:但是,当我尝试使requestChoice()方法处理不同情况时,类型推断无法按预期工作:

public requestChoice<T extends ChoiceType>(request: ChoiceRequest<T>) {
  switch (request.type) {
    case 'a':             // OK, but should complain since values can only be 'hero' or 'monster'
      ...
    case 1:               // Here it complains, see below (*)
      ...
    ...
  }
}

(*) Type 'number' is not comparable to type 'T'. (*) 类型 'number' 不能与类型 'T' 进行比较。 'T' could be instantiated with an arbitrary type which could be unrelated to 'number'. 'T' 可以用与 'number' 无关的任意类型实例化。

I have had this problem repeatedly before, but I don't fully understand why this happens.我以前反复遇到过这个问题,但我不完全明白为什么会发生这种情况。 I thought it had something to do with conditional types, so I then tried a less elegant second approach :我认为它与条件类型有关,所以我尝试了一种不太优雅的第二种方法

interface ChoiceMap {
  hero: HeroType[];
  monster: MonsterType[];
}

type ChoiceType = keyof ChoiceMap;

interface ChoiceRequest<T extends ChoiceType> {
  player: Player;
  type: T;
  options: ChoiceMap[T];
}

However, this approach worked exactly as the first.但是,这种方法与第一种方法完全一样。

The only way to make this work as expected was a third approach , building ChoiceRequest explicitly as a tagged union, without generics or conditional types:使这项工作按预期进行的唯一方法是第三种方法,将ChoiceRequest显式构建为标记联合,没有泛型或条件类型:

interface MonsterRequest {
  player: Player;
  type: 'monster';
  options: MonsterType[];
}

interface HeroRequest {
  player: Player;
  type: 'hero';
  options: HeroType[];
}

type ChoiceRequest = MonsterRequest | HeroRequest;

QUESTIONS: Why does the third approach work and the first two don't?问题:为什么第三种方法有效而前两种方法无效? What I am missing about how type inference works?关于类型推断的工作原理,我缺少什么? Are there other patterns to achieve what I need in scenarios like this?在这样的场景中,是否还有其他模式可以实现我所需要的?

If you don't need T in the return type a very simple workaround is probably:如果您不需要返回类型中的 T 一个非常简单的解决方法可能是:

function requestChoice(request: ChoiceRequest<ChoiceType>) {
  switch (request.type) {
    case 'a':             // Type '"a"' is not comparable to type ChoiceType
    case 1:               // Type '1' is not comparable to type ChoiceType
    case "hero": // fine
  }
}

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

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