[英]Function return type that depends on function argument property
I'm creating a wrapper for an API in TypeScript.我正在为 TypeScript 中的 API 创建一个包装器。
One API call can return 2 types of responses: basic and extended, depending on the argument property.一次 API 调用可以返回两种类型的响应:基本响应和扩展响应,具体取决于参数属性。
If the argument property is true, the response will be extended.如果参数属性为真,响应将被扩展。 If the argument property is not provided or false, the response will be basic.
如果未提供参数属性或为 false,则响应将是基本的。
This is the code example:这是代码示例:
interface Args {
token: string;
region: string;
extendedResponse?: boolean;
}
interface BasicResponse {
ID: string;
}
interface ExtendedResponse extends BasicResponse {
extendedResponseProperty: object;
}
function returnResponse(args: Args): BasicResponse | ExtendedResponse {
if (args.extendedResponse) {
return { ID: "foobar", extendedResponseProperty: {} };
}
return {ID: "foobar"}
}
let res1 = returnResponse({ token: "asdf", region: "us", extendedResponse: true });
Playground: https://www.typescriptlang.org/play/index.html?ssl=24&ssc=84&pln=1&pc=1#code/JYOwLgpgTgZghgYwgAgIJQOYGdkG8CwAUMicmAPYDWEIAXMlmFKBgNxGnJQQbDl0MmLdsVIQAHpBAATCNIBKELAAd+WCAH56AI3LkANhDggRAXyJFQkWIhQAhOFmAJFKtSgKiSASQAi9RmYQNiJzQktwaHgkZABRSRpZBSVVEHVkCSlpHAcnFxT3PA4xBJk5V1T1AAUocmVoMABPenJtACsIBDAzC3DCGABXEC6+EC4IMAGoEAr3AAo4TCx6dGwASnpc51m0lAAfONKknfTPTmKSYBhkBaWAOkzE8oLdtaKvTnHJ6bxkP3oAEQwPTaRYAgA0GSOzzcuxqdQazTwpmQphEnDCnG43zGuH+yCBILBYTCREMYHGWAAjMgALxfKYzF7qOa4MhUGiAxzSGAQ8a8fiAgZYPmPMrJWHqehMAYoUxrES9XpAA Playground: https://www.typescriptlang.org/play/index.html?ssl=24&ssc=84&pln=1&pc=1#code/JYOwLgpgTgZghgYwgAgIJQOYGdkG8CwAUMicmAPYDWEIAXMlmFKBgNxGnJQQbDl0MmLdsVIQAHpBAATCNIBKELAAd+WCAH56AI3LkANhDggRAXyJFQkWIhQAhOFmAJFKtSgKiSASQAi9RmYQNiJzQktwaHgkZABRSRpZBSVVEHVkCSlpHAcnFxT3PA4xBJk5V1T1AAUocmVoMABPenJtACsIBDAzC3DCGABXEC6+EC4IMAGoEAr3AAo4TCx6dGwASnpc51m0lAAfONKknfTPTmKSYBhkBaWAOkzE8oLdtaKvTnHJ6bxkP3oAEQwPTaRYAgA0GSOzzcuxqdQazTwpmQphEnDCnG43zGuH+yCBILBYTCREMYHGWAAjMgALxfKYzF7qOa4MhUGiAxzSGAQ8a8fiAgZYPmPMrJWHqehMAYoUxrES9XpAA
However this code does not show the consumer of this method that an ExtendedResponse
was returned, it shows the type as BasicResponse | ExtendedResponse
但是,此代码并未向该方法的使用者显示返回了
ExtendedResponse
,而是将类型显示为BasicResponse | ExtendedResponse
BasicResponse | ExtendedResponse
and does not show the extendedResponseProperty
in intellisense. BasicResponse | ExtendedResponse
并且不在智能感知中显示extendedResponseProperty
。
The closest I got to solving this was using this code:我最接近解决这个问题的方法是使用以下代码:
interface Args {
token: string;
region: string;
}
interface ExtendedArgs extends Args {
extendedResponse: true;
}
interface BasicResponse {
ID: string;
}
interface ExtendedResponse extends BasicResponse {
extendedResponseProperty: object;
}
function returnResponse(args: Args): BasicResponse;
function returnResponse(args: ExtendedArgs): ExtendedResponse;
function returnResponse(args: Args | ExtendedArgs): BasicResponse | ExtendedResponse {
if (args as ExtendedArgs) {
return { ID: "foobar", extendedResponseProperty: {} };
}
return {ID: "foobar"}
}
let res1 = returnResponse({ token: "asdf", region: "us" });
let res2 = returnResponse({ token: "asdf", region: "asdf", extendedResponse: true });
Link to playground: https://www.typescriptlang.org/play/index.html?ssl=32&ssc=86&pln=1&pc=1#code/JYOwLgpgTgZghgYwgAgIJQOYGdkG8CwAUMicmAPYDWEIAXMlmFKBgNxGnJQQbDl0MmLdsVJEAvkSKhIsRCgCiAD0ggAJhDXpsyCCppqc2nAVEk9qjWoBKELAAd+WCPSYBXCCIlTCM6PCRkACE4LGAEWwcnFFNOAEkAEXpGZhA2b0JpcH95ZGVLTUjHEGddfXUcELCIu2LS2NILA0La6IAFKHJ7aDAAT3pyACMAKwgEMBFJTMIYNxBxvhAuCDA3KBAi6IAKOEwsemMASnoq8M2SzyJZ+bBF5dX18+cdvfp85q0947zyqyfLmZzBb8e5rDatC4vbAHPbIAA+PwKn2w31ONSiF3hiI+-zwHFIwBgyChOFC2PUmiOeLMnFB6zwyES9AARDByENdsyADRlJH-DpdHr9PDiZDiEScKacfEkbgPJa4JnIVnswacqZTIgAGxWyywAEZkABeOngjHPXBkKg0FmhNQwbnLXj8FluLDMsWHLyEHVgPUAJmNpv+W0tFGoAmZdodPO4zsj0cdTQpNghzlcUA8ntYQA Link to playground: https://www.typescriptlang.org/play/index.html?ssl=32&ssc=86&pln=1&pc=1#code/JYOwLgpgTgZghgYwgAgIJQOYGdkG8CwAUMicmAPYDWEIAXMlmFKBgNxGnJQQbDl0MmLdsVJEAvkSKhIsRCgCiAD0ggAJhDXpsyCCppqc2nAVEk9qjWoBKELAAd+WCPSYBXCCIlTCM6PCRkACE4LGAEWwcnFFNOAEkAEXpGZhA2b0JpcH95ZGVLTUjHEGddfXUcELCIu2LS2NILA0La6IAFKHJ7aDAAT3pyACMAKwgEMBFJTMIYNxBxvhAuCDA3KBAi6IAKOEwsemMASnoq8M2SzyJZ+bBF5dX18+cdvfp85q0947zyqyfLmZzBb8e5rDatC4vbAHPbIAA+PwKn2w31ONSiF3hiI+-zwHFIwBgyChOFC2PUmiOeLMnFB6zwyES9AARDByENdsyADRlJH-DpdHr9PDiZDiEScKacfEkbgPJa4JnIVnswacqZTIgAGxWyywAEZkABeOngjHPXBkKg0FmhNQwbnLXj8FluLDMsWHLyEHVgPUAJmNpv+W0tFGoAmZdodPO4zsj0cdTQpNghzlcUA8ntYQA
But I don't really like this solution because the consumers of this method would need to deal with function overload and two types of input args.但我不太喜欢这种解决方案,因为这种方法的使用者需要处理 function 重载和两种类型的输入参数。
There is only one input Args, it just happens that it can have extendedResponse: boolean
attribute. input Args只有一个,正好可以有
extendedResponse: boolean
属性。 Two input args make it not as intuitive.两个输入参数使其不那么直观。
Is there a TypeScript type-based approach that solves this?有没有基于 TypeScript 类型的方法可以解决这个问题?
UPDATE更新
A slightly better solution is to use a union type for the input Args:稍微好一点的解决方案是对输入参数使用联合类型:
interface Args {
token?: string;
region?: string;
}
interface BasicResponse {
ID: string;
}
interface ExtendedResponse extends BasicResponse {
extendedResponseProperty: object;
}
function returnResponse(args: Args): BasicResponse;
function returnResponse(args: Args & {callbackData: true}): ExtendedResponse;
function returnResponse(args: Args | Args & {callbackData: true}): BasicResponse | ExtendedResponse {
if (args as { callbackData: true }) {
return { ID: "foobar", extendedResponseProperty: {}}
}
return {ID: "foobar"}
}
let res1 = returnResponse({ token: "asdf", region: "us" });
let res2 = returnResponse({ token: "foo", callbackData: true });
It's more readable for the consumer as it shows that adding callbackData: true
will return a different result, but still not ideal because it is still two different argument types.它对消费者来说更具可读性,因为它表明添加
callbackData: true
将返回不同的结果,但仍然不理想,因为它仍然是两种不同的参数类型。
You could use a generic like this if you don't like the overloads:如果您不喜欢重载,可以使用这样的泛型:
interface Args {
token: string;
region: string;
extendedResponse?: true;
}
interface BasicResponse {
ID: string;
}
interface ExtendedResponse extends BasicResponse {
extendedResponseProperty: object;
}
function returnResponse<A extends Args>(args: A): A extends { extendedResponse: true} ? ExtendedResponse : BasicResponse {
if (args.extendedResponse === true) {
return { ID: "foobar", extendedResponseProperty: {} } as any;
}
return {ID: "foobar"} as any
}
let res1 = returnResponse({ token: "asdf", region: "us" });
let res2 = returnResponse({ token: "asdf", region: "asdf", extendedResponse: true });
But honestly, I like the code in your codesandbox.但老实说,我喜欢你的代码盒中的代码。 It describes what happens without additional magic and is simple to read.
它描述了在没有额外魔法的情况下发生的事情,并且易于阅读。
Also, I'm not sure what you mean with "the consumer will have to deal with method overload" - the consumer gets what they are calling, not more or less.另外,我不确定“消费者将不得不处理方法重载”是什么意思——消费者得到他们正在调用的东西,而不是更多或更少。 You are describing a certain behaviour here, and no matter how you describe - different input arguments will lead to different output, and as soon as your runtime code does that, the consumer will have to deal with that anyways.
您在这里描述了某种行为,无论您如何描述 - 不同的输入参数将导致不同的输出,一旦您的运行时代码这样做,消费者无论如何都必须处理它。
Interesting problem.有趣的问题。 I could only think of this solution.
我只能想到这个解决方案。 I would use generics using only the extendedResponse field as type discriminator but it is more or less the same than previous answer:/
我会使用 generics 仅使用 extendedResponse 字段作为类型鉴别器,但它或多或少与以前的答案相同:/
// We make the args generic of E
interface Args<E extends true | false> {
token: string;
region: string;
extendedResponse?: E;
}
// No changes here
interface BasicResponse { ID: string }
interface ExtendedResponse extends BasicResponse {
extendedResponseProperty: Record<string, unknown>;
}
// This is not really required but I like to have explainatory intermediate types
type IReturnResponse<T extends boolean> = T extends true ? ExtendedResponse : T extends false ? BasicResponse : never;
/**
* This function is now generic.
* As a developer it might not be great but the implementation is not leaked to the user.
*/
function returnResponse<E extends boolean>(args: Args<E>): IReturnResponse<E> {
if (args.extendedResponse === true) {
return { ID: "foobar", extendedResponseProperty: {} } as IReturnResponse<E>;
}
return { ID: "foobar" } as IReturnResponse<E>;
}
// The library user will see this ⤵️
// Infered to Extended response
const res1 = returnResponse({
token: "asdf",
region: "us",
extendedResponse: true,
});
// Infered to Basic response
const res2 = returnResponse({
token: "asdf",
region: "us",
extendedResponse: false,
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.