【问题标题】:How does type inference work with union types (+conditional types and generics) in TypeScript?TypeScript 中的类型推断如何与联合类型(+条件类型和泛型)一起工作?
【发布时间】:2020-12-24 17:03:02
【问题描述】:

我正在使用 Angular 开发游戏,并尝试将演示与游戏逻辑分离。为了实现这一点,我构建了一个单独的UiController 服务来处理用户交互和演示。每当需要显示某些内容或需要用户操作时,与游戏逻辑相关的服务都会向 UiController 发出请求。

为了尽可能简洁地实现这一点,我正在尝试抽象出与UiController 交互的接口。一种常见的交互是选择,当玩家必须从同一类别的不同选项中选择一个时使用。该交互由UiControllerrequestChoice() 方法处理,该方法需要ChoiceRequest 类型的参数。由于有许多不同的类别可供选择,因此该类型必须包含所有这些类别,并且该方法必须知道如何处理所有这些类别。

例如,用户可能需要选择怪物或英雄。我使用文字类型来引用选项中的选项:

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

我想到的构建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>;
}

这在构建这样的选择请求时证明是有用的,因为 typeoptions 中的项目的值被正确预测或拒绝:

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

但是,当我尝试让 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 (*)
      ...
    ...
  }
}

(*) 类型“数字”与类型“T”不可比。 'T' 可能是 用可能无关的任意类型实例化 '数字'。

我以前曾多次遇到此问题,但我不完全理解为什么会发生这种情况。我认为它与条件类型有关,所以我尝试了一种不太优雅的第二种方法

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

type ChoiceType = keyof ChoiceMap;

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

但是,这种方法与第一种方法完全一样。

使这项工作按预期进行的唯一方法是第三种方法,将ChoiceRequest明确构建为标记联合,不使用泛型或条件类型:

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

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

type ChoiceRequest = MonsterRequest | HeroRequest;

问题:为什么第三种方法有效而前两种方法无效?关于类型推断的工作原理,我缺少什么?在这样的场景中是否有其他模式可以实现我所需要的?

【问题讨论】:

  • 似乎只是打字稿通用约束的失败:function requestChoice(request: ChoiceRequest&lt;"monster"&gt;) 错误的方式与您期望的一样

标签: typescript generics type-inference union-types conditional-types


【解决方案1】:

如果您不需要 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
  }
}

【讨论】:

    猜你喜欢
    • 2021-06-05
    • 2019-09-17
    • 1970-01-01
    • 2021-10-18
    • 1970-01-01
    • 2019-04-10
    • 2021-07-28
    • 2018-01-18
    • 2018-10-20
    相关资源
    最近更新 更多