打字稿:无法提取带有标签类型保护的对象的值类型
Typescript: can't extract value type of object with labeled type guard
我不知道为什么无法使用泛型正确提取对象值类型,如下所示。
如果你知道,能告诉我为什么它不起作用吗?
export type ValueType = {
type: "string",
value: string,
} | {
type: "number",
value: number,
}
/** 1. Extract value type of "string" type */
type StringValueType = (ValueType & {type: "string"})["value"];
// type StringValueType = string
/** 2. Extract value type of each type by generic */
type ValueTypeWithGeneric<T extends ValueType["type"]> = (ValueType & {type: T})["value"];
type StringValueTypeWithGeneric = ValueTypeWithGeneric<"string">;
// type StringValueTypeWithGeneric = string | number
// I expect this type to be string
这是 TypeScript 的设计限制;请参阅 microsoft/TypeScript#45428 以获得权威答案。当遇到如下形式的类型时
type X<T> = ({ a: 0, b: "x" } & { a: T })["b"]
编译器假定这将始终等同于
type X<T> = ({ a: 0 & T, b: "x" })["b"]
无论 generic 类型参数 T
是什么。因此编译器急切地将其缩减为
type X<T> = "x"
所以 X<T>
只是 "x"
独立于 T
:
type XT0 = X<0> // "x"
type XT1 = X<1> // "x"
但是当您实际使用 non-generic 类型代替 T
执行此操作时,您会看到不同的行为:
type X0 = ({ a: 0, b: "x" } & { a: 0 })["b"] // "x"
type X1 = ({ a: 0, b: "x" } & { a: 1 })["b"] // never
这是X<1>
和X1
之间的差异运行。
在 TypeScript 3.8 及更低版本中, 没有差异。输入 X1
也将计算为 "x"
:
// TS3.8 and below
type X1 = ({ a: 0, b: "x" } & { a: 1 })["b"] // "x" in TS3.8-
即使 a
属性 的值不可能同时为 "0"
和 "1"
类型,编译器仍保留 {a: 0, b: "x"} & {a: 1}
as-is,因此 b
属性 被视为 "x"
.
类型
但是 TypeScript 3.9 引入了对 reducing intersections by discriminant properties, as implemented in microsoft/TypeScript#. Now the compiler sees {a: 0, b: "x"} & {a: 1}
as the never
type 的支持,因此 b
属性 也是 never
类型。
这太棒了。但不幸的是,面对泛型的行为没有更新以说明这一变化。当与泛型 {a: T}
相交时,无论如何都不会发生缩减。那好吧。这是设计限制。
此处推荐的解决方法是避免为此目的使用交集,而是使用 the Extract<T, U>
utility type,其目的是将联合类型 T
过滤为仅可分配给 U
的成员.因此,您可以编写 Extract<{a: 0, b: "x"}, {a: T}>
而不是 {a: 0, b: "x"} & {a: T}
并获得一致的行为:
type Ex<T> = Extract<{ a: 0, b: "x" }, { a: T }>["b"]
type Ex0 = Ex<0> // "x"
type Ex1 = Ex<1> // never
这意味着您的示例也将开始按预期运行:
/** 1. Extract value type of "string" type */
type StringValueType = Extract<ValueType, { type: "string" }>["value"];
// type StringValueType = string
/** 2. Extract value type of each type by generic */
type ValueTypeWithGeneric<T extends ValueType["type"]> = Extract<ValueType, { type: T }>["value"];
type StringValueTypeWithGeneric = ValueTypeWithGeneric<"string">;
// type StringValueTypeWithGeneric = string
我不知道为什么无法使用泛型正确提取对象值类型,如下所示。 如果你知道,能告诉我为什么它不起作用吗?
export type ValueType = {
type: "string",
value: string,
} | {
type: "number",
value: number,
}
/** 1. Extract value type of "string" type */
type StringValueType = (ValueType & {type: "string"})["value"];
// type StringValueType = string
/** 2. Extract value type of each type by generic */
type ValueTypeWithGeneric<T extends ValueType["type"]> = (ValueType & {type: T})["value"];
type StringValueTypeWithGeneric = ValueTypeWithGeneric<"string">;
// type StringValueTypeWithGeneric = string | number
// I expect this type to be string
这是 TypeScript 的设计限制;请参阅 microsoft/TypeScript#45428 以获得权威答案。当遇到如下形式的类型时
type X<T> = ({ a: 0, b: "x" } & { a: T })["b"]
编译器假定这将始终等同于
type X<T> = ({ a: 0 & T, b: "x" })["b"]
无论 generic 类型参数 T
是什么。因此编译器急切地将其缩减为
type X<T> = "x"
所以 X<T>
只是 "x"
独立于 T
:
type XT0 = X<0> // "x"
type XT1 = X<1> // "x"
但是当您实际使用 non-generic 类型代替 T
执行此操作时,您会看到不同的行为:
type X0 = ({ a: 0, b: "x" } & { a: 0 })["b"] // "x"
type X1 = ({ a: 0, b: "x" } & { a: 1 })["b"] // never
这是X<1>
和X1
之间的差异运行。
在 TypeScript 3.8 及更低版本中, 没有差异。输入 X1
也将计算为 "x"
:
// TS3.8 and below
type X1 = ({ a: 0, b: "x" } & { a: 1 })["b"] // "x" in TS3.8-
即使 a
属性 的值不可能同时为 "0"
和 "1"
类型,编译器仍保留 {a: 0, b: "x"} & {a: 1}
as-is,因此 b
属性 被视为 "x"
.
但是 TypeScript 3.9 引入了对 reducing intersections by discriminant properties, as implemented in microsoft/TypeScript#. Now the compiler sees {a: 0, b: "x"} & {a: 1}
as the never
type 的支持,因此 b
属性 也是 never
类型。
这太棒了。但不幸的是,面对泛型的行为没有更新以说明这一变化。当与泛型 {a: T}
相交时,无论如何都不会发生缩减。那好吧。这是设计限制。
此处推荐的解决方法是避免为此目的使用交集,而是使用 the Extract<T, U>
utility type,其目的是将联合类型 T
过滤为仅可分配给 U
的成员.因此,您可以编写 Extract<{a: 0, b: "x"}, {a: T}>
而不是 {a: 0, b: "x"} & {a: T}
并获得一致的行为:
type Ex<T> = Extract<{ a: 0, b: "x" }, { a: T }>["b"]
type Ex0 = Ex<0> // "x"
type Ex1 = Ex<1> // never
这意味着您的示例也将开始按预期运行:
/** 1. Extract value type of "string" type */
type StringValueType = Extract<ValueType, { type: "string" }>["value"];
// type StringValueType = string
/** 2. Extract value type of each type by generic */
type ValueTypeWithGeneric<T extends ValueType["type"]> = Extract<ValueType, { type: T }>["value"];
type StringValueTypeWithGeneric = ValueTypeWithGeneric<"string">;
// type StringValueTypeWithGeneric = string