简体   繁体   English

如何告诉打字稿我正确使用条件类型而不是联合?

[英]How tell typescript that I use conditional type over union correctly?

I have this base case:我有这个基本案例:

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");

As you can see, Typescript is not satisfied with my a function implementation.如您所见,Typescript 对我的a函数实现并不满意。 What should I change to compile this function but still keep guarantees in the last three statements?我应该改变什么来编译这个函数,但仍然保留最后三个语句中的保证?

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

It's an open issue in TypeScript (see microsoft/TypeScript#33912) that the compiler is generally unable to verify that a particular function return value conforms to a conditional type that depends on an as-yet-unspecified generic type parameter, like Ret<T> inside the implementation of a() where T is unresolved.这是TypeScript 中的一个悬而未决的问题(请参阅 microsoft/TypeScript#33912) ,编译器通常无法验证特定函数的返回值是否符合依赖于尚未指定的泛型类型参数的条件类型,例如Ret<T>a()的实现中,其中T未解析。 This is related to the fact that TypeScript is unable to narrow type parameters via control flow analaysis] (see microsoft/TypeScript#24085 ), so checking id with a switch / case statement might narrow the type of id to, say, "bar" , but it does not narrow the type parameter T to "bar" , and thus it can't guarantee that Ret<"bar"> is an acceptable output.这与 TypeScript 无法通过控制流分析缩小类型参数的事实有关](请参阅microsoft/TypeScript#24085 ),因此使用switch / case语句检查id可能会将id的类型缩小为"bar" ,但它不会将类型参数T缩小到"bar" ,因此它不能保证Ret<"bar">是可接受的输出。


One thing you can do is accept that the compiler can't verify this for you and use type assertions or an overload to loosen the implementation typing enough to avoid the errors.您可以做的一件事是接受编译器无法为您验证这一点,并使用类型断言重载来充分放松实现类型以避免错误。 This will work, but the compiler is not guaranteeing type safety.这会起作用,但编译器不保证类型安全。 For example, with an 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 };
  }
}

now there's no error, and there's some type safety... you can't return a completely incorrect type like {spazz: true} , but you can swap the case s around and it won't notice:现在没有错误,并且有一些类型安全......你不能返回一个完全不正确的类型,比如{spazz: true} ,但是你可以交换case并且它不会注意到:

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 };
  }
}

So you have to be careful.所以你必须小心。


Another solution to this particular case is to abandon conditional types in favor of generic indexing, like this:这种特殊情况的另一个解决方案是放弃条件类型以支持通用索引,如下所示:

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");

The compiler is able to verify that what we are doing here is safe, because we're indexing into an object of type RetMap with the id key of type K .编译器能够验证我们在这里所做的事情是安全的,因为我们使用K类型的id键索引到RetMap类型的对象。 Oh, and if you are unhappy that this version preemptively calculates return values it won't use, you could refactor to use getters , which the compiler is also happy with:哦,如果你不满意这个版本抢先计算它不会使用的返回值,你可以重构使用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];
}

Okay, hope that helps you proceed;好的,希望能帮助你继续; good luck!祝你好运!

Playground link to code Playground 链接到代码

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

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