英文:
How to infer or to deduce the return type from an argument in typescript
问题
我有一个类型,可以从对象的一个字段中推断出来。
我编写了这个函数,它接受zoneType并返回关联的zone。
但是最后,每次调用时,我得到的是:MyType1 | MyType2 | MyType3
,而不是关联的类型。实际上,这个类型可以从参数中推断出来,因为switch case可以轻松找到类型。
顺便说一下,我们在谈论geojson。
我在考虑尝试重载,但是typescript不允许有重复的函数。
另外,使用infer关键字,但它对于typescript本身可以猜测的事情来说似乎有点“冗长”。
我将感激您的帮助。
谢谢!
type MyType1 = "geojson.Feature<geojson.MultiPolygon>";
type MyType2 = "geojson.Feature<geojson.Polygon>";
type MyType3 = "geojson.FeatureCollection<geojson.Polygon>";
export type ZoneGeojson = MyType1 | MyType2 | MyType3;
type InternalZone = {
id: number;
zone: ZoneGeojson | null;
zone_type: ZoneZoneType;
};
export interface Zone1 extends InternalZone {
zone_type: ZoneZoneType.Type1 | ZoneZoneType.Type2;
zone: MyType1 | null;
}
export interface Zone2 extends InternalZone {
zone_type: ZoneZoneType.Type3;
zone: MyType1 | null;
}
export interface Zone3 extends InternalZone {
zone_type: ZoneZoneType.Type4;
zone: MyType1 | null;
}
export const exportZone = (
zoneType: ZoneZoneType,
zone: FeatureCollection<Polygon>,
) => {
switch (zoneType) {
case ZoneZoneType.Type1:
case ZoneZoneType.Type2:
return zone1.zoneGeojsonFactory.exportZoneEdition(zone);
case ZoneZoneType.Type3:
return zone2.zoneGeojsonFactory.exportZoneEdition(zone);
case ZoneZoneType.Type4:
return zone3.zoneGeojsonFactory.exportZoneEdition(zone);
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unknown zone type ${zoneType ?? '[undefined]'}`);
}
};
英文:
I have a type that is deductible from one field of the object.
And I wrote this function that takes the zoneType and returns the associate zone.
But at the end, and on each call I get : MyType1 | MyType2 | MyType3
and not the associated type. This type is actually deductible from the argument, since the switch case can easily find the type.
We are talking about geojson btw.
I was wondering trying overloading, but typescript does not allow having duplicated functions.
Also, infer, but it looks "huge" for something that can be guessed by TS itself?
I will appreciate your help.
Thanks !
type MyType1 = "geojson.Feature<geojson.MultiPolygon>";
type MyType2 = "geojson.Feature<geojson.Polygon>";
type MyType3 = "geojson.FeatureCollection<geojson.Polygon>";
export type ZoneGeojson = MyType1 | MyType2 | MyType3;
type InternalZone = {
id: number;
zone: ZoneGeojson | null;
zone_type: ZoneZoneType;
};
export interface Zone1 extends InternalZone {
zone_type: ZoneZoneType.Type1 | ZoneZoneType.Type2;
zone: MyType1 | null;
}
export interface Zone2 extends InternalZone {
zone_type: ZoneZoneType.Type3;
zone: MyType1 | null;
}
export interface Zone3 extends InternalZone {
zone_type: ZoneZoneType.Type4;
zone: MyType1 | null;
}
export const exportZone = (
zoneType: ZoneZoneType,
zone: FeatureCollection<Polygon>,
) => {
switch (zoneType) {
case ZoneZoneType.Type1:
case ZoneZoneType.Type2:
return zone1.zoneGeojsonFactory.exportZoneEdition(zone);
case ZoneZoneType.Type3:
return zone2.zoneGeojsonFactory.exportZoneEdition(zone);
case ZoneZoneType.Type4:
return zone3.zoneGeojsonFactory.exportZoneEdition(zone);
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unknown zone type ${zoneType ?? '[undefined]'}`);
}
};
答案1
得分: 1
这是基于参数的返回类型的一些特定案例。
应用于您的情况:
type ObjectType<T> =
T extends ZoneZoneType.Type1 ? Zone1 :
T extends ZoneZoneType.Type2 ? Zone1 :
T extends ZoneZoneType.Type3 ? Zone2 :
T extends ZoneZoneType.Type4 ? Zone3 :
never;
export const exportZoneEditionGeojson = <T extends ZoneZoneType>(
zoneType: T,
zoneEditionGeojson: FeatureCollection<Polygon>,
): ObjectType<T> => {
switch (zoneType) {
case ZoneZoneType.Type1:
case ZoneZoneType.Type2:
return zone1.zoneGeojsonFactory
.exportZoneEdition(zoneEditionGeojson) as ObjectType<T>;
case ZoneZoneType.Type3:
return zone2.zoneGeojsonFactory
.exportZoneEdition(zoneEditionGeojson) as ObjectType<T>;
case ZoneZoneType.Type4:
return zone3.zoneGeojsonFactory
.exportZoneEdition(zoneEditionGeojson) as ObjectType<T>;
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unknown zone type ${zoneType}`);
}
};
英文:
This is some specific case of return type based on parameter
Applied to your case :
type ObjectType<T> =
T extends ZoneZoneType.Type1 ? Zone1 :
T extends ZoneZoneType.Type2 ? Zone1 :
T extends ZoneZoneType.Type3 ? Zone2 :
T extends ZoneZoneType.Type4 ? Zone3 :
never;
export const exportZoneEditionGeojson = <T extends ZoneZoneType>(
zoneType: T,
zoneEditionGeojson: FeatureCollection<Polygon>,
): ObjectType<T> => {
switch (zoneType) {
case ZoneZoneType.Type1:
case ZoneZoneType.Type2:
return zone1.zoneGeojsonFactory
.exportZoneEdition(zoneEditionGeojson) as ObjectType<T>;
case ZoneZoneType.Type3:
return zone2.zoneGeojsonFactory
.exportZoneEdition(zoneEditionGeojson) as ObjectType<T>;
case ZoneZoneType.Type4:
return zone3.zoneGeojsonFactory
.exportZoneEdition(zoneEditionGeojson) as ObjectType<T>;
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unknown zone type ${zoneType}`);
}
};
答案2
得分: 1
你可以使用一个将输入映射到输出类型的地图和一个函数重载。
我已经添加了对 ZoneZoneType
和 zoneX
函数的定义,然后将您的代码简化为一个最小可复现示例。尽管对于这个示例,实际上第二个参数是不必要的。
enum ZoneZoneType {
Type1,
Type2,
Type3,
Type4,
}
const zone1 = (input: string) => "zone1" as const;
const zone2 = (input: string) => "zone2" as const;
const zone3 = (input: string) => "zone3" as const;
type ResultMap = {
[ZoneZoneType.Type1]: ReturnType<typeof zone1>;
[ZoneZoneType.Type2]: ReturnType<typeof zone1>;
[ZoneZoneType.Type3]: ReturnType<typeof zone2>;
[ZoneZoneType.Type4]: ReturnType<typeof zone3>;
};
export function exportZone<T extends ZoneZoneType>(
zoneType: T,
zone: string
): ResultMap[T];
export function exportZone(zoneType: ZoneZoneType, zone: string) {
switch (zoneType) {
case ZoneZoneType.Type1:
case ZoneZoneType.Type2:
return zone1(zone);
case ZoneZoneType.Type3:
return zone2(zone);
case ZoneZoneType.Type4:
return zone3(zone);
default:
throw new Error(`Unknown zone type ${zoneType ?? "[undefined]"}`);
}
}
const res1 = exportZone(ZoneZoneType.Type1, "foo");
// ^? const res1: "zone1"
const res2 = exportZone(ZoneZoneType.Type2, "foo");
// ^? const res2: "zone1"
const res3 = exportZone(ZoneZoneType.Type3, "foo");
// ^? const res3: "zone2"
const res4 = exportZone(ZoneZoneType.Type4, "foo");
// ^? const res4: "zone3"
函数的实现与您的原始实现相同。但是,调用者看到的签名是通过索引到新的 ResultMap
类型确定的返回类型。
这不强制实际返回指定的类型,因此您需要确保地图和返回值是正确的。@Neiluj 的答案对返回值使用了类型断言,因此也不安全。请选择适合您情况的方法。
总的来说,重载是不安全的,所以在您的尝试中要小心...
我看到的与使用条件类型的实现相比的优势包括:
- 映射类型的更简单语法;
- 基于记录的映射类型在更复杂的场景中比条件类型更好用;
- 更容易切换到使用“依赖类型函数”功能,如果它被实现的话。
像 @jcalz 这样的人可能能够进一步评论这两种方法的区别。
有关添加对此的适当支持的请求,请参阅 TS #33014("建议添加依赖类型函数的支持:保守缩小泛型索引访问结果类型")。
有关有关解决通用类型参数定义参数的一般问题的更多信息,请参阅 @jcalz 对"TypeScript泛型在调用时工作但在函数体内不工作?"的回答。
英文:
You could use a map of inputs to output types, and a function overload.
I have added a definition for ZoneZoneType
and the zoneX
functions and then trimmed your code down to a Minimal Reproducible Example. Although, for this, even the second parameter is actually unnecessary.
enum ZoneZoneType {
Type1,
Type2,
Type3,
Type4,
}
const zone1 = (input: string) => "zone1" as const;
const zone2 = (input: string) => "zone2" as const;
const zone3 = (input: string) => "zone3" as const;
type ResultMap = {
[ZoneZoneType.Type1]: ReturnType<typeof zone1>;
[ZoneZoneType.Type2]: ReturnType<typeof zone1>;
[ZoneZoneType.Type3]: ReturnType<typeof zone2>;
[ZoneZoneType.Type4]: ReturnType<typeof zone3>;
};
export function exportZone<T extends ZoneZoneType>(
zoneType: T,
zone: string
): ResultMap[T];
export function exportZone(zoneType: ZoneZoneType, zone: string) {
switch (zoneType) {
case ZoneZoneType.Type1:
case ZoneZoneType.Type2:
return zone1(zone);
case ZoneZoneType.Type3:
return zone2(zone);
case ZoneZoneType.Type4:
return zone3(zone);
default:
throw new Error(`Unknown zone type ${zoneType ?? "[undefined]"}`);
}
}
const res1 = exportZone(ZoneZoneType.Type1, "foo");
// ^? const res1: "zone1"
const res2 = exportZone(ZoneZoneType.Type2, "foo");
// ^? const res2: "zone1"
const res3 = exportZone(ZoneZoneType.Type3, "foo");
// ^? const res3: "zone2"
const res4 = exportZone(ZoneZoneType.Type4, "foo");
// ^? const res4: "zone3"
The function implementation is as you had. However, the signature the caller sees has the return type determined from the input, by indexing into the new ResultMap
type.
This does not enforce actually returning the specified type, so it's up to you to ensure that the map and the returns are correct.
The answer by @Neiluj uses type assertions on the returns, so it is also unsafe.
Pick what works for your case.
Overloads are unsafe in general, so do be careful on your quest...
The advantages I see over the implementation using conditional types:
- the simpler syntax of the mapping type;
- record-based mapping types work better than conditional types in more complex scenarios;
- ease of switching to utilizing the "dependent-type-like functions" feature, should it be implemented.
Someone like @jcalz will likely be able to comment further on the differences.
See TS #33014 ("Suggestion for Dependent-Type-Like Functions: Conservative Narrowing of Generic Indexed Access Result Type") for the request to add proper support for this.
See @jcalz's answer to "TypeScript generics work when you call it but not in the function body?" for more info on workarounds for the general problem of defining parameters using generic type parameters.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论