条件类型与泛型类型的推断不同

Conditional type is inferred differently with generic type

Typescript 在传递泛型类型时以不同方式推断条件类型(禁用分布)。

type Box<T> = { a: T };

// Conditional type with disabled distribution using tuple.
type CondType<T> = [T] extends [string | number]
    ? Box<T> : false;

class Test<T extends string | number> {
    public constructor(private value: T) {}

    // CondType<T> is not inferred as Box<string | number>
    public one(param: CondType<T>): void {
        param; // type is false | Box<T>
    }

    // This works as expected
    public two(param: CondType<string | number>): void {
        param; // type is Box<string | number>
    }
}

这是为什么?有解决办法吗?

这是 TypeScript 目前缺少的功能。有关相关功能请求,请参阅 microsoft/TypeScript#23132。它的状态是“等待更多反馈”所以去那里可能是个好主意,给它一个,并描述你的用例为什么你需要这个,如果你认为它特别引人注目(它可能需要更有动力一点不过比这里的例子还要多)。

问题是编译器不参考 generic constraints when evaluating conditional types 依赖于尚未指定的泛型类型参数。相反,编译器只是 完全延迟 评估。因此,即使 T 被限制为 string | number,编译器也不会使用此信息急切地采用 CondType<T> 的真实分支并导致 Box<T>。对于未指定的 T.

,它保持 CondType<T> 未知可分配给 Box<T>

可能的解决方法,直到 ms/TS#23132 得到解决:

好吧,因为实际上 CondType<T> 肯定会求值为 Box<T>,您可以只使用该类型:

  public three(param: Box<T>): void {
    param; // Box<T> of course
  }

虽然我认为这可能不适用于您的实际用例(这就是为什么我会说 ms/TS#23132 中的任何评论都应该具有无法简单解决的情况像这样。

对于编译器更了解某些表达式类型的情况,通用的解决方法是使用a type assertion。从本质上讲,您正在将验证类型安全的工作从编译器中拿走并将责任推到自己身上:

  public four(param: CondType<T>): void {
    const paramAsserted = param as Box<T>; // just assert it
  }

这会让你按要求继续前进,尽管危险是你可能不小心欺骗了编译器;假设您稍后将 class Test<T extends string | number> {/*...*/} 更改为 class Test<T extends string | number | boolean> {/*...*/}。该类型断言将不再为真,但编译器将不会报告错误。毕竟,编译器无法区分何时 CondType<T> 可分配给泛型 TBox<T> 和何时不可分配。所以你必须小心类型断言,并仔细检查你断言的内容是否正确。

Playground link to code