typescript 在这个例子中,为什么`value`没有被自动缩小到类型`number`?

jecbmhm3  于 2023-06-24  发布在  TypeScript
关注(0)|答案(1)|浏览(134)

我不明白为什么在这个TypeScript函数中“value”没有缩小到number。我在想,如果TProperty.Height,那么Data[T]的类型应该是Data[Property.Height],也就是number

enum Property {
    Height = 'height',
    Name = 'name',
    DateOfBirth = 'date_of_birth'
}

interface Data {
    [Property.Height]: number;
    [Property.Name]: string;
    [Property.DateOfBirth]: Date;
}

function example<T extends Property>(value: Data[T], property: T) {
    if (property === Property.Height) {
        // why isn't "value" narrowed to number here?
    }
}
e4yzc0pl

e4yzc0pl1#

TypeScript目前无法使用控制流分析来影响generic * 类型参数 *,例如example的主体内的T。因此,虽然检查property === Property.Height将缩小property的类型,但T类型参数本身将顽固地保持不变。
T没有改变的一个原因是,如果检查property === Property.Height意味着T就是Property.Height,这是不正确的。约束T extends Property并不意味着“T恰好是Property的联合成员之一”。它可以是Property的任何子类型,包括完整的Property联合本身。让我们看看如果我们用union类型调用example()`会发生什么:

// this does not cause a compiler error:
example(new Date(), Math.random() < 0.001 ? Property.DateOfBirth : Property.Height); 

// function example<Property.Height | Property.DateOfBirth>(
//    value: number | Date, property: Property.Height | Property.DateOfBirth
// ): void

该调用是 allowed,您可以看到T被推断为union Property.Height | Property.DateOfBirth,这意味着Data[T]被推断为number | Date。在这个调用中,有99.9%的机会property === Property.Height,而value * 绝对 * Date。哎呀。
microsoft/TypeScript#27808上有一个长期的开放功能请求,要求其他语法而不是T extends Property。比如说T oneof Property,意思是T必须是Property联合体的一个成员。然后,也许编译器可以使用property === Property.Height得出结论,TProperty.Height,你会得到你想要的行为。但现在它不是语言的一部分。
现在,如果你想在你的函数中使用if/elseswitch/case case分析,泛型不会真正帮助你。相反,支持的方式是与受歧视的工会。我可以将代码重构为:

type ExampleArgs = { [K in Property]: [value: Data[K], property: K] }[Property]
/* type ExampleArgs = 
  [value: number, property: Property.Height] | 
  [value: string, property: Property.Name] | 
  [value: Date, property: Property.DateOfBirth] 
*/

function example(...[value, property]: ExampleArgs) {
  if (property === Property.Height) {
    value.toFixed(); // works
  }
}

example(new Date(), 
  Math.random() < 0.001 ? Property.DateOfBirth : Property.Height); // error!  
example(new Date(), Property.DateOfBirth); // okay

我不想离题太多来解释它是如何计算的,但是您可以看到现在example不是通用的;相反,它采用区分联合类型的rest parameter,其中第二个参数 * 区分 * 参数列表。你不能再用它打坏电话了,如上所示。
Playground链接到代码

相关问题