具有泛型的类型别名表现出与非泛型类型不同的行为
Type aliases with generics exhibit different behavior from non-generic type
考虑以下代码:
type TestTuple = [
{ test: "foo" },
{
test: "bar";
other: 1;
}
];
type Foo<Prop extends string> = TestTuple extends Record<Prop, string>[]
? true
: false;
type X = Foo<"test">;
type Prop = "test";
type Y = TestTuple extends Record<Prop, string>[]
? true
: false;
// X is type false
const x: X = false;
// Y is type true
const y: Y = true;
类型 Foo
和 Y
完全相同,除了 Foo
有一个通用参数 Prop
,而 Y
只是使用一个名为Prop
(不需要类型别名,Y
可以只是 TestTuple extends Record<"test", string>[] ? true : false
,但我想让它们的声明完全相同)。所以,Foo<"test">
(别名为 X
类型)和 Y
应该具有相同的类型,对吗?显然不是。 X
作为类型 false
而 Y
是类型 true
。将 TestTuple
中的 other
属性 更改为字符串或完全删除 属性 会导致 X
和 Y
都为真,这是预期行为。
所以,我的问题是:这是为什么?这是编译器中的错误吗?如果是这样,是否已经提交了我无法找到的问题?或者,这是在打字稿中处理泛型的某种奇怪方式吗?
不幸的是,我个人不知道。它与 Record
或 { [K in Prop]: any }
等字典类型有关,因为根据我的经验,它只对这些类型失败。希望有人能提出更好的答案。
但我可以提供一个解决方法。
解决方法
不是比较 TestTuple
和 Record<...>[]
,而是比较 TestTuple[number]
和 Record<...>
type Foo<P extends string> = TestTuple[number] extends Record<P, string>
? true
: false
// true
type X = Foo<'test'>
// also true
type Y = TestTuple extends Record<'test', string>[]
? true
: false;
更新:这已针对 TypeScript 4.2 修复:Playground link to code
在简化为以下最小示例后,我已就此提交 microsoft/TypeScript#41613:
type What<K extends string> =
{ x: { y: 0, z: 1 } } extends { x: { [P in K]: 0 } } ? true : false;
type Huh = What<"y">; // expect true but got false!
TypeScript 首席架构师Anders Hejlsberg has commented:
When determining whether to defer resolution of the conditional type we relate the "most permissive instantiations" of the check and extends types. The constraint of the most permissive instantiation of the extends type ends up being { x: { [index: string]: 0 } }
, but really it should be { x: { } }
. It's a simple fix and I'll include it in this PR.
所以希望它最终会在 a new PR 中得到修复,并可能通过 TypeScript 4.2 合并到 TypeScript 中。 (更新:它已被合并。)如果是这样,我希望这应该解决您问题中的问题,我们不是用 {x: ...}
包装索引类型,而是用元组类型包装它。
在那之前,您应该考虑使用 中的解决方法。
考虑以下代码:
type TestTuple = [
{ test: "foo" },
{
test: "bar";
other: 1;
}
];
type Foo<Prop extends string> = TestTuple extends Record<Prop, string>[]
? true
: false;
type X = Foo<"test">;
type Prop = "test";
type Y = TestTuple extends Record<Prop, string>[]
? true
: false;
// X is type false
const x: X = false;
// Y is type true
const y: Y = true;
类型 Foo
和 Y
完全相同,除了 Foo
有一个通用参数 Prop
,而 Y
只是使用一个名为Prop
(不需要类型别名,Y
可以只是 TestTuple extends Record<"test", string>[] ? true : false
,但我想让它们的声明完全相同)。所以,Foo<"test">
(别名为 X
类型)和 Y
应该具有相同的类型,对吗?显然不是。 X
作为类型 false
而 Y
是类型 true
。将 TestTuple
中的 other
属性 更改为字符串或完全删除 属性 会导致 X
和 Y
都为真,这是预期行为。
所以,我的问题是:这是为什么?这是编译器中的错误吗?如果是这样,是否已经提交了我无法找到的问题?或者,这是在打字稿中处理泛型的某种奇怪方式吗?
不幸的是,我个人不知道。它与 Record
或 { [K in Prop]: any }
等字典类型有关,因为根据我的经验,它只对这些类型失败。希望有人能提出更好的答案。
但我可以提供一个解决方法。
解决方法
不是比较 TestTuple
和 Record<...>[]
,而是比较 TestTuple[number]
和 Record<...>
type Foo<P extends string> = TestTuple[number] extends Record<P, string>
? true
: false
// true
type X = Foo<'test'>
// also true
type Y = TestTuple extends Record<'test', string>[]
? true
: false;
更新:这已针对 TypeScript 4.2 修复:Playground link to code
在简化为以下最小示例后,我已就此提交 microsoft/TypeScript#41613:
type What<K extends string> =
{ x: { y: 0, z: 1 } } extends { x: { [P in K]: 0 } } ? true : false;
type Huh = What<"y">; // expect true but got false!
TypeScript 首席架构师Anders Hejlsberg has commented:
When determining whether to defer resolution of the conditional type we relate the "most permissive instantiations" of the check and extends types. The constraint of the most permissive instantiation of the extends type ends up being
{ x: { [index: string]: 0 } }
, but really it should be{ x: { } }
. It's a simple fix and I'll include it in this PR.
所以希望它最终会在 a new PR 中得到修复,并可能通过 TypeScript 4.2 合并到 TypeScript 中。 (更新:它已被合并。)如果是这样,我希望这应该解决您问题中的问题,我们不是用 {x: ...}
包装索引类型,而是用元组类型包装它。
在那之前,您应该考虑使用