我有一个像这样的简单的受歧视联盟:
enum Kind {
A = "a",
B = "b",
C = "c"
}
type Value =
| { kind: Kind.A, value: 1 }
| { kind: Kind.B, value: 2 }
| { kind: Kind.C, value: 3 }
我想转换为一个对象类型,其中 Kind
中的每个键都具有来自 Value["value"]
的对应值。对于上面的示例,它应类似于 { a: 1, b: 2, c: 3 }
。
为了从可区分联合中选择一个值,我使用与具有具体种类属性的对象的交集:
type t1 = (Value & { kind: Kind.A })["value"]
// ^? type t1 = 1
但是当我在映射类型中执行相同的操作时,它会输出 Value["value"]
:
type t2 = { [K in Kind]: (Value & { kind: K })["value"] }
// ^? type t2 = { a: 1 | 2 | 3, b: 1 | 2 | 3, c: 1 | 2 | 3 }
我在这里缺少什么?
这是我的问题的有效解决方案:
type t3 = { [K in Kind]: (Value & { kind: K }) }
// This almost works, the main problem has magically disappeared by doing this in two iterations instead of one
// But now TS says that "value" cannot be used to index t3[K]
type t4 = { [K in Kind]: t3[K]["value"] }
// Actually works
type t5 = { [K in Kind]: t3[K] extends infer T extends { value: unknown } ? T["value"] : never }
还通过首先合并交集解决了索引问题:
type Merge<T> = { [K in keyof T]: T[K] }
type t6 = { [K in Kind]: Merge<Value & { kind: K }> }
type t7 = { [K in Kind]: t6[K]["value"] }
我已将问题范围缩小到以下范围:
enum Kind {
A = "a",
B = "b",
C = "c"
}
type Value =
| { kind: Kind.A, value: 1 }
| { kind: Kind.B, value: 2 }
| { kind: Kind.C, value: 3 }
type t1 = Kind.A extends infer K extends Kind.A ? (Value & { kind: K })["value"] : never
// ^? type t1 = 1 | 2 | 3
type t2 = (Value & { kind: Kind.A })["value"]
// ^? type t2 = 1
显然,如果 Kind.A 是一个类型参数,TS 会以不同的方式对待它(即使它具有与上例相同的约束)。
这是为什么?
最佳答案
这是 TypeScript 的设计限制,如 microsoft/TypeScript#45428 中所述。 .
indexing 的问题进入intersection像 (Value & { kind: K })["value"]
这样的类型是编译器根本没有意识到它应该在以下情况下引用 {kind: K}
确定 value
属性类型,因为 {kind: K}
没有 value
属性。
给定一个类似 (A & B)[P]
的类型,其中 P
扩展 keyof A
但不扩展 keyof B
code> (并且 A & B
尚未立即计算)编译器将采用直接将其计算为 A[P]
的捷径。这几乎总是正确的做法,但您也发现了并非如此的情况。或者至少在不明显正确的地方。
您认为 {foo: never, bar: 2}["bar"]}
应该是什么?看起来应该是 2
,但是如果有一条规则将 {foo: never, bar: 2}
折叠为 never
,因为它不可能存在吗?那么也许应该是(never)["bar"]
,即never
。有争议的是,它可能是 2
或 never
,具体取决于折叠规则何时适用。您的情况与此相同,只是带有 union这些类型: ({foo: "a", bar: 1) | {foo: never, bar: 2})["bar"])
可以是 1 | 2
或只是 1
,具体取决于折叠规则何时适用。
但即使我们决定它应该是 never
或 1
,编译器也不会费心去弄清楚这一点,因为这样做会使评估类型更加困难一直都很昂贵,只是偶尔提高正确性。所以这是 TypeScript 的设计限制。解决方法是使用 the Extract
utility type如 GitHub 问题和该问题的其他答案中所述。
关于typescript - 为什么映射类型中与可区分联合的交集会给出所有类型的联合而不是缩小它?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75767183/