typescript 型别缩小和'Extract' Helper与泛型型别互动时发生未预期的型别错误

omvjsjqw  于 2022-11-26  发布在  TypeScript
关注(0)|答案(1)|浏览(133)

下面的问题让我很困惑。我有一个简单的标签联合:

type MyUnion = 
    | { tag: "Foo"; field: string; } 
    | { tag: "Bar"; } 
    | null;

现在我有了这个泛型函数:

const foo = <T extends MyUnion>(value: T) => {
    // Narrowing works as expected.
    if (value === null) { return; }
    const notNull: NonNullable<T> = value;

    // Narrowing works with field access, but not with `Extract` type.
    if (value.tag !== "Foo") { return; }
    const s: string = value.field;  // Field can be accessed -> all good here.
    const extracted: Extract<T, { tag: "Foo" }> = value;  // error!
};

Playground
最后一行导致此错误:

Type 'T & {}' is not assignable to type 'Extract<T, { tag: "Foo"; }>'.

我真的不明白为什么。在函数外编写它而不使用泛型Extract<MyUnion, { tag: "Foo" }>会得到正确的类型。这是怎么回事?

  • 注意 *:在我的真实的代码中,该函数有另一个参数回调:(v:提取〈T,{标签:“Foo”}〉)=〉void。调用该回调会导致相同类型的错误。所以我的目标是以某种方式使它工作:因此,函数foo基本上对一个值执行特定的检查,以某种方式处理特殊情况,然后用经过清理的值调用回调。我假设TypeScript以某种方式允许我表达这种抽象?
tpgth1q7

tpgth1q71#

这可以看作是TypeScript中的一个设计限制。这个问题是Extract是一个依赖于泛型类型T的条件类型。
T在代码中使用条件时几乎是未指定的。当然,T有一个约束,强制它成为MyUnion的子类型。但是还不知道 concrete 类型,因为它是由函数的调用者指定的。每当编译器遇到这样的条件类型时,该类型本质上保持不透明,直到 * 只有 * Extract<T, { tag: "Foo" }>类型的值可以安全地赋给它为止。
你可以在#33484#28884中找到类似问题的讨论。有很多关于泛型和条件的情况,其中类型对阅读代码的人来说可能是合理的,但编译器因为尚未解析的类型而停止推理。
您可能想知道为什么会这样:

const notNull: NonNullable<T> = value;

#49119NonNullable从 * 条件类型 * 改变为简单地使T{}相交的类型。

- type NonNullable<T> = T extends null | undefined ? never : T;
+ type NonNullable<T> = T & {};

相同的PR还引入了控制流分析的改进。检查一个泛型类型的变量是否不是null将与{}的变量类型相交。
由于NonNullable<T>value的类型都计算为T & {},因此这使得可赋值性成为可能

相关问题