如何区分联合与泛型?
How to discriminate unions with generics?
我正在努力根据几何形状(在显示某些 GeoJSON 数据的上下文中)自动推断不同种类项目的类型。
我使用的是通用类型,因此我没有设法设置自定义类型保护,因为它可以让我区分“个体”项目和“聚合”,但不能区分不同类型的“个体”项目。
基本上,我需要推理的层次:
- 从聚合中区分单个项目
- 区分每个类别中的不同几何形状。
我创建了一个简化示例,在我的真实应用程序中,我有 4 种不同类型的项目,它们可能具有不同的可能几何形状。
Here is a TypeScript playground,以及下面的代码:
type A = {type: "A", a: string}
type B = {type: "B", b: string}
type C = {type: "C", c: string}
type Geometries = A | B | C
type IndividualFeature<G extends A | B = A | B> = { geometry: G, indivAttribute: string}
type AggregateFeature = { geometry: C, aggAttribute: string}
type DisplayableFeature = IndividualFeature | AggregateFeature
const display = (feature: DisplayableFeature) => {
switch(feature.geometry.type) {
case "A":
console.log("type A", feature.geometry.a, feature.indivAttribute);
return;
case "B":
console.log("type B", feature.geometry.b, feature.indivAttribute)
return;
case "C":
console.log("type C", feature.geometry.c, feature.aggAttribute)
default:
// should not happen
}
}
const indivFeature: IndividualFeature = { geometry: { type: "A", a: "a"}, indivAttribute: "hello indiv"}
const aggFeature: AggregateFeature = { geometry: { type: "C", c: "c"}, aggAttribute: "hello agg"}
几何被正确区分,但不是单独与聚合(feature.indivAttribute
/feature.aggAttribute
触发错误)。
作为记录,我尝试了类型保护:这让我能够区分“Indiv”和“Aggregates”,但我失去了对几何形状的区分。
我应该如何构造我的 types/code 以便 feature.indivAttribute
在本例中被正确识别为有效属性?
也许只是投射功能?
switch(feature.geometry.type) {
case "A": console.log("type A", feature.geometry.a, (feature as IndividualFeature).indivAttribute);
return;
case "B":
console.log("type B", feature.geometry.b, (feature as IndividualFeature).indivAttribute)
return;
case "C":
console.log("type C", feature.geometry.c, (feature as AggregateFeature).aggAttribute)
default:
// should not happen
}
这确实是打字稿的限制,即使没有泛型。这里存在一个 github 问题:microsoft/TypeScript#18758. There is also a PR with some recent activity: microsoft/TypeScript#38839.
目前无法基于嵌套的可区分联合来缩小联合。判别式必须处于同一“级别”。
作为解决方法,您可以像这样编写 custom type guard:
type AllTypes = DisplayableFeature["geometry"]["type"] // "A" | "B" | "C"
type FeatueOfType<T extends AllTypes> = {
"A": IndividualFeature<A>,
"B": IndividualFeature<B>,
"C": AggregateFeature
}[T]
function isFeatueOfType<T extends AllTypes>(
feature: DisplayableFeature, type: T
): feature is FeatueOfType<T> {
return feature.geometry.type === type
}
FeatueOfType<T>
将几何类型映射到它的要素类型。例如。 FeatueOfType<"A">
等于 IndividualFeature<A>
。 (这种类型可能会直接从 DisplayableFeature
生成,而不是手工编写,但这可能会变得复杂。)
当你像 isFeatueOfType(feature, "A")
这样调用类型保护并且 feature.geometry.type === type
检查成功时,我们告诉打字稿 feature
的类型必须是 FeatueOfType<"A">
,即 IndividualFeature<A>
.
(请注意,当类型保护中存在错误时,例如上面写的 feature.geometry.type !== type
,typescript 无法捕捉到它。因此始终建议对其进行适当的测试。)
用法:
const display = (feature: DisplayableFeature) => {
if (isFeatueOfType(feature, "A")) {
doSometingWithA(feature) // typechecks
console.log("type A", feature.geometry.a, feature.indivAttribute);
}
else if (isFeatueOfType(feature, "B")) {
console.log("type B", feature.geometry.b, feature.indivAttribute)
}
else if (isFeatueOfType(feature, "C")) {
console.log("type C", feature.geometry.c, feature.aggAttribute)
}
else {
throw Error()
}
}
function doSometingWithA(a: IndividualFeature<A>) {}
我正在努力根据几何形状(在显示某些 GeoJSON 数据的上下文中)自动推断不同种类项目的类型。
我使用的是通用类型,因此我没有设法设置自定义类型保护,因为它可以让我区分“个体”项目和“聚合”,但不能区分不同类型的“个体”项目。
基本上,我需要推理的层次:
- 从聚合中区分单个项目
- 区分每个类别中的不同几何形状。
我创建了一个简化示例,在我的真实应用程序中,我有 4 种不同类型的项目,它们可能具有不同的可能几何形状。
Here is a TypeScript playground,以及下面的代码:
type A = {type: "A", a: string}
type B = {type: "B", b: string}
type C = {type: "C", c: string}
type Geometries = A | B | C
type IndividualFeature<G extends A | B = A | B> = { geometry: G, indivAttribute: string}
type AggregateFeature = { geometry: C, aggAttribute: string}
type DisplayableFeature = IndividualFeature | AggregateFeature
const display = (feature: DisplayableFeature) => {
switch(feature.geometry.type) {
case "A":
console.log("type A", feature.geometry.a, feature.indivAttribute);
return;
case "B":
console.log("type B", feature.geometry.b, feature.indivAttribute)
return;
case "C":
console.log("type C", feature.geometry.c, feature.aggAttribute)
default:
// should not happen
}
}
const indivFeature: IndividualFeature = { geometry: { type: "A", a: "a"}, indivAttribute: "hello indiv"}
const aggFeature: AggregateFeature = { geometry: { type: "C", c: "c"}, aggAttribute: "hello agg"}
几何被正确区分,但不是单独与聚合(feature.indivAttribute
/feature.aggAttribute
触发错误)。
作为记录,我尝试了类型保护:这让我能够区分“Indiv”和“Aggregates”,但我失去了对几何形状的区分。
我应该如何构造我的 types/code 以便 feature.indivAttribute
在本例中被正确识别为有效属性?
也许只是投射功能?
switch(feature.geometry.type) {
case "A": console.log("type A", feature.geometry.a, (feature as IndividualFeature).indivAttribute);
return;
case "B":
console.log("type B", feature.geometry.b, (feature as IndividualFeature).indivAttribute)
return;
case "C":
console.log("type C", feature.geometry.c, (feature as AggregateFeature).aggAttribute)
default:
// should not happen
}
这确实是打字稿的限制,即使没有泛型。这里存在一个 github 问题:microsoft/TypeScript#18758. There is also a PR with some recent activity: microsoft/TypeScript#38839.
目前无法基于嵌套的可区分联合来缩小联合。判别式必须处于同一“级别”。
作为解决方法,您可以像这样编写 custom type guard:
type AllTypes = DisplayableFeature["geometry"]["type"] // "A" | "B" | "C"
type FeatueOfType<T extends AllTypes> = {
"A": IndividualFeature<A>,
"B": IndividualFeature<B>,
"C": AggregateFeature
}[T]
function isFeatueOfType<T extends AllTypes>(
feature: DisplayableFeature, type: T
): feature is FeatueOfType<T> {
return feature.geometry.type === type
}
FeatueOfType<T>
将几何类型映射到它的要素类型。例如。 FeatueOfType<"A">
等于 IndividualFeature<A>
。 (这种类型可能会直接从 DisplayableFeature
生成,而不是手工编写,但这可能会变得复杂。)
当你像 isFeatueOfType(feature, "A")
这样调用类型保护并且 feature.geometry.type === type
检查成功时,我们告诉打字稿 feature
的类型必须是 FeatueOfType<"A">
,即 IndividualFeature<A>
.
(请注意,当类型保护中存在错误时,例如上面写的 feature.geometry.type !== type
,typescript 无法捕捉到它。因此始终建议对其进行适当的测试。)
用法:
const display = (feature: DisplayableFeature) => {
if (isFeatueOfType(feature, "A")) {
doSometingWithA(feature) // typechecks
console.log("type A", feature.geometry.a, feature.indivAttribute);
}
else if (isFeatueOfType(feature, "B")) {
console.log("type B", feature.geometry.b, feature.indivAttribute)
}
else if (isFeatueOfType(feature, "C")) {
console.log("type C", feature.geometry.c, feature.aggAttribute)
}
else {
throw Error()
}
}
function doSometingWithA(a: IndividualFeature<A>) {}