【问题标题】:How tell typescript that I use conditional type over union correctly?如何告诉打字稿我正确使用条件类型而不是联合?
【发布时间】:2020-05-15 02:39:38
【问题描述】:

我有这个基本情况:

type Ids = "foo" | "bar" | "bazz";

interface IFoo {
  foo: string;
}

interface IBar {
  bar: number;
}

interface IBazz {
  bazz: boolean;
}

type Ret<T extends Ids> = T extends "foo" ? IFoo :
  T extends "bar" ? IBar :
  T extends "bazz" ? IBazz :
  never;

function a<T extends Ids>(id: T): Ret<T> {
  switch (id) {
    case "bar":
      return { bar: 1 };
    case "foo":
      return { foo: "foo" };
    case "bazz":
      return { bazz: true };
  }
}

const bar: IBar = a("bar");
const foo: IFoo = a("foo");
const bazz: IBazz = a("bazz");

如你所见,Typescript 对我的a 函数实现并不满意。我应该改变什么来编译这个函数但仍然在最后三个语句中保持保证?

游乐场:https://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAkgJgZygXigIgGYHstqgH3QCMBDAJz0LVIC8a0BuAKCYEsA7YCMjEgY2gwAYjigBvJlCjYsALigJgZDgHNmAXxYcuPfoIBC5cZKiky89gFcAtkW4atnbrwGxDdY1No15RHABsIEnYHJlBIKAAlCGAAHgAVKAgADy52RFhEAD4UKESUtIzMHDwAflgRLChZE3zUiHSkanIytyMaqTrCpu9WmHcaapN2CAA3exYMS3Y+YFYsdigSBKT6xsyELIAKVjh5eIBKeWi4+JyJKQQAd1ZgPgALKB24A88pKD4SBGhmig73qRkGKWMiLMSmcjyACMUHUzABn2+6BkaH+AKBwBBYOkOHkxVwsPh70RP16aPeGKx4ghdHkSks0DhJk0miYfAWigh5jaZFyJC2vzQB2Y7PYnJk8mEolQ-PxQpFHOANJ8bQ8MoFvWFQA

【问题讨论】:

  • 您能解释一下您为什么要这样做T extends "foo",我看不出这背后有什么实际原因。我觉得应该是T === "foo"
  • @Nicolas 这是 TypeScript 中条件类型的语法。

标签: typescript types conditional-statements union


【解决方案1】:

这是一个open issue in TypeScript (see microsoft/TypeScript#33912),编译器通常无法验证特定函数返回值是否符合依赖于尚未指定的泛型类型参数的条件类型,例如Ret&lt;T&gt; 在@987654328 的实现中@ 其中T 未解决。这与 TypeScript 无法通过控制流分析缩小类型参数的事实有关](请参阅microsoft/TypeScript#24085),因此使用switch/case 语句检查id 可能会将id 的类型缩小为,比如说"bar",但它不会将类型参数T缩小到"bar",因此它不能保证Ret&lt;"bar"&gt;是一个可接受的输出。


您可以做的一件事是接受编译器无法为您验证这一点,并使用type assertionsan overload 来放松实现键入以避免错误。这将起作用,但编译器不保证类型安全。例如,有一个重载:

function aOverload<T extends Ids>(id: T): Ret<T>;
function aOverload(id: Ids): Ret<Ids> {
  switch (id) {
    case "bar":
      return { bar: 1 };
    case "foo":
      return { foo: "foo" };
    case "bazz":
      return { bazz: true };
  }
}

现在没有错误,并且有一些类型安全...您不能返回像{spazz: true}这样的完全不正确的类型,但是您可以交换cases,它就赢了'不注意:

function aBadOverload<T extends Ids>(id: T): Ret<T>;
function aBadOverload(id: Ids): Ret<Ids> {
  switch (id) {
    case "bazz":
      return { bar: 1 };
    case "bar":
      return { foo: "foo" };
    case "foo":
      return { bazz: true };
  }
}

所以你必须小心。


针对这种特殊情况的另一种解决方案是放弃条件类型以支持泛型索引,如下所示:

interface RetMap {
  foo: IFoo,
  bar: IBar,
  bazz: IBazz;
}

function aGood<K extends keyof RetMap>(id: K): RetMap[K] {
  return {
    bar: { bar: 1 },
    foo: { foo: "foo" },
    bazz: { bazz: true }
  }[id];
}

const bar: IBar = aGood("bar");
const foo: IFoo = aGood("foo");
const bazz: IBazz = aGood("bazz");

编译器能够验证我们在此执行的操作是否安全,因为我们使用id 类型的K 键对RetMap 类型的对象进行索引。哦,如果你不满意这个版本抢先计算它不会使用的返回值,你可以重构使用getters,编译器也很满意:

function aGood<K extends keyof RetMap>(id: K): RetMap[K] {
  return {
    get bar() { return { bar: 1 } },
    get foo() { return { foo: "foo" } },
    get bazz() { return { bazz: true } }
  }[id];
}

好的,希望对您有所帮助;祝你好运!

Playground link to code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-18
    • 2023-02-13
    • 1970-01-01
    • 2023-03-30
    • 2017-08-15
    • 2015-08-17
    • 1970-01-01
    • 2019-08-27
    相关资源
    最近更新 更多