首页 > 解决方案 > 取决于函数参数属性的函数返回类型

问题描述

我正在为 TypeScript 中的 API 创建一个包装器。

一个 API 调用可以返回 2 种类型的响应:基本和扩展,具体取决于参数属性。

如果参数属性为真,则响应将被扩展。如果未提供参数属性或为 false,则响应将是基本的。

这是代码示例:

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

但是,此代码未向该方法的使用者显示ExtendedResponse返回的 an,而是将类型显示为BasicResponse | ExtendedResponse并且不显示extendedResponsePropertyintellisense。

我最接近解决这个问题的是使用以下代码:

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

但我不太喜欢这种解决方案,因为这种方法的使用者需要处理函数重载和两种类型的输入参数。

只有一个输入参数,它恰好可以具有extendedResponse: boolean属性。两个输入参数使它不那么直观。

有没有一种基于 TypeScript 类型的方法可以解决这个问题?


更新

更好的解决方案是对输入 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 });

它对消费者来说更具可读性,因为它表明添加callbackData: true将返回不同的结果,但仍然不理想,因为它仍然是两种不同的参数类型。

标签: typescript

解决方案


如果您不喜欢重载,可以使用这样的泛型:

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 });

但老实说,我喜欢你的代码盒中的代码。它描述了在没有额外魔法的情况下会发生什么,并且易于阅读。

另外,我不确定您所说的“消费者将不得不处理方法重载”是什么意思——消费者得到了他们正在调用的东西,而不是或多或少。您在这里描述了某种行为,无论您如何描述 - 不同的输入参数将导致不同的输出,并且一旦您的运行时代码这样做,消费者无论如何都必须处理它。


推荐阅读