TypeScript索引访问类型约束行为异常

时间:2019-07-15 13:13:41

标签: typescript generics type-constraints discriminated-union

以这个例子

interface Person<TName> {
    name: TName;
}

type People =
    | Person<"Levi">
    | Person<"Julien">

type FilteredPersonBase<TPerson extends People> = TPerson["name"] extends "Levi" ? TPerson : never;
type FilteredPerson = FilteredPersonBase<People>; // never

type FilteredPersonBase2<TPerson extends People> = TPerson extends { name: "Levi" } ? TPerson : never;
type FilteredPerson2 = FilteredPersonBase2<People>; // Person<"Levi">

我希望FilteredPersonFilteredPerson2都可以解析为Person<"Levi">,但事实并非如此。为什么在类型约束中使用索引访问运算符的解析方式与使用内联类型的解析方式不同?

1 个答案:

答案 0 :(得分:3)

我同意第二个应该解决Person<"Levi">人,但是让我们看看这是怎么发生的:

FilteredPersonBase2<People>
    // FilteredPersonBase2 is a distributive conditional type since
    // TPerson appears nakedly in the condition of the conditional type
    => FilteredPersonBase2<Person<"Levi">> | FilteredPersonBase2<Person<"Julien">>
    // The conditional type is applied to each member of the union
    => (Person<"Levi"> extends { name: "Levi" } ? Person<"Levi"> : never) |  (Person<"Julien"> extends { name: "Levi" } ? Person<"Julien"> : never)
    // The first application return Person<"Levi">, the second resolves to never
    => Person<"Levi"> | never
    // Never is removed in a union 
    => Person<"Levi">

此方法有效的原因是distributive behavior of conditional types

但是条件类型仅分布在裸类型参数上。在第一个示例中,条件在TPerson["name"]之上,它不是裸类型参数,因此不会发生分布。扩展条件类型,我们得到:

FilteredPersonBase<People> =>
    // No distribution 
    People["name"] extends "Levi" ? People : never => 
    // People["name"] just resolves to the union
    "Levi" | "Julien" extends "Levi" ? People : never => 
    // The union does not extend a constituent of the union
    never 

由于未发生分配,因此TPerson["name"]只会解析为"Levi" | "Julien",并且并集不会扩展组成部分。 ("Levi" extends TPerson["name"]的另一种说法是正确的,但这只会解决联盟(Person),因为联盟的组成部分是联盟的子类型。

只是为了好玩,您可以使用TPerson之类始终为真的条件来强制在TPerson extends TPerson上分发:

type FilteredPersonBase<TPerson extends People> = TPerson extends TPerson ? TPerson["name"] extends "Levi" ? TPerson : never : never;
type FilteredPerson = FilteredPersonBase<People>;  // Person<"Levi">