下面的问题让我很困惑。我有一个简单的标签联合:
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以某种方式允许我表达这种抽象?
1条答案
按热度按时间tpgth1q71#
这可以看作是TypeScript中的一个设计限制。这个问题是
Extract
是一个依赖于泛型类型T
的条件类型。T
在代码中使用条件时几乎是未指定的。当然,T
有一个约束,强制它成为MyUnion
的子类型。但是还不知道 concrete 类型,因为它是由函数的调用者指定的。每当编译器遇到这样的条件类型时,该类型本质上保持不透明,直到 * 只有 *Extract<T, { tag: "Foo" }>
类型的值可以安全地赋给它为止。你可以在#33484或#28884中找到类似问题的讨论。有很多关于泛型和条件的情况,其中类型对阅读代码的人来说可能是合理的,但编译器因为尚未解析的类型而停止推理。
您可能想知道为什么会这样:
#49119将
NonNullable
从 * 条件类型 * 改变为简单地使T
与{}
相交的类型。相同的PR还引入了控制流分析的改进。检查一个泛型类型的变量是否不是
null
将与{}
的变量类型相交。由于
NonNullable<T>
和value
的类型都计算为T & {}
,因此这使得可赋值性成为可能