有没有一种方法可以根据对象参数的值映射函数返回?

时间:2019-10-07 15:10:50

标签: typescript

我有一个类型,它忽略了界面的一个键。我想声明一个函数签名,其中Typescript可以推断返回的是类型或接口:

interface SomeInterface {
    a: number;
    b: string;
    c: boolean;
    d: string;
}

type SomeTypeFromInterface = Omit<SomeInterface, 'd'>;

let test: SomeTypeFromInterface;

interface Config{
    withD: boolean;
    some: string;
    other: string;
    values: string;
}

function getObject(config: Config): SomeInterface | SomeTypeFromInterface  {

    if (config.withD) {
        return {} as unknown as SomeInterface;
    } else {
        return {} as unknown as SomeTypeFromInterface;
    }
}

const myConfig: Config = {
    withD: true,
    some: 'some',
    other: 'other',
    values: 'values',
}

const myObject = getObject(myConfig);
myObject.d // <-- should exist

当前typescript doesn't infer correctly the return type

typescript doesn't infercorrectly the return type

这可能吗?

我试图这样做:

type Function1 = (t1: Config) => { [D in keyof Config]: null extends Config[D] ? SomeTypeFromInterface : SomeInterface };

const  getObject: Function1 = (config: Config)=> {

    if (config.withD) {
        return {} as unknown as SomeInterface;
    } else {
        return {} as unknown as SomeTypeFromInterface;
    }
}

但是它不起作用。

编辑:

Link to code related to my comment@jcalz answer

1 个答案:

答案 0 :(得分:3)

在一些地方,您需要通过跟踪withDtrue还是false(而不是boolean)来指导编译器,以及如何getObject()的返回值取决于此值。


首先,您需要myConfig的类型来取决于其withD属性是true还是false;您不能将其类型注释为Config,因为它的宽度足以表示两种情况。编译器会立即忘记const myConfig: Config = {...}拥有withD作为true的情况。最简单的方法就是根本不使用类型注释。您无需myConfig符合Config。 TypeScript具有structural类型的系统;如果myConfig符合Config,您仍然可以将其传递给需要Config的函数。

所以首先我们要这样做:

const myConfig = {
    withD: true,
    some: 'some',
    other: 'other',
    values: 'values',
}

让我们检查myConfig的类型:

/* const myConfig: {
    withD: boolean; // whoops
    some: string;
    other: string;
    values: string;
} */

嗯,withD仍被推断为boolean。这将我们带到需要干预的下一个地方:


您希望在类型级别上区分truefalse,这意味着truefalse本身应被视为boolean literal types。请注意,在TypeScript中,boolean类型与联合类型true | false等效。

当您编写类似{ withD: true }的值时,编译器将首先将该属性解释为true文字类型,但随后通常将其解释为widen the type to boolean unless you tell it not to

有多种方法可以告诉您不要这样做。从TypeScript 3.4开始,最简单的方法之一就是使用const assertion。如果将as const添加到文字值,则倾向于将其推断为尽可能窄。因此,让我们尝试一下:

const myConfig = {
    withD: true as const,
    some: 'some',
    other: 'other',
    values: 'values',
}

结果

/* const myConfig: {
    withD: true;
    some: string;
    other: string;
    values: string;
} */

太好了。请注意,您可以将as const放置在其他位置(例如const myConfig = {...} as const),也可以做其他类似{withD: true as true}的操作,但这已经足够。


现在已知myConfig具有true的{​​{1}}值,因此我们必须修改withD来解决这个问题。一种可行的方法是使用overloads。 (我只是提供重载解决方案;您也可以将generic函数与conditional types一起使用,但是重载可能更容易理解)

getObject()

为方便起见,我添加了interface YesConfig extends Config { withD: true } interface NoConfig extends Config { withD: false } // call signatures function getObject(config: YesConfig): SomeInterface; function getObject(config: NoConfig): SomeTypeFromInterface; function getObject(config: Config): SomeInterface | SomeTypeFromInterface; // implementation function getObject(config: Config): SomeInterface | SomeTypeFromInterface { if (config.withD) { return {} as unknown as SomeInterface; } else { return {} as unknown as SomeTypeFromInterface; } } YesConfig类型来表示NoConfig中的差异,然后有三个呼叫签名。如果withDconfig,则输出为YesConfig。如果SomeInterfaceconfig,则输出为NoConfig。如果SomeTypeFromInterface仅是config(因此,如果编译器无法识别),则输出为两种类型的并集。而且实现签名必须能够处理所有调用签名。

现在,最后,您可以执行以下操作:

Config

它有效!编译器选择第一个调用签名,输出类型为const myObject = getObject(myConfig); myObject.d // <-- no error!


为完整起见,让我们看看使用此方法的其他方式:

SomeInterface

这里,const anotherObject = getObject({ withD: false, other: "", some: "", values: "" }); // const anotherObject: Pick<SomeInterface, "a" | "b" | "c"> anotherObject.d; // error, as expected 的{​​{1}}中有anotherObject,输出为withD;所以我们使用了上面的第二个呼叫签名。请注意,我们不必使用false,因为对象文字立即被传递给SomeTypeFromInterface。编译器知道某些as const调用签名关心getObject()属性的文字类型,因此它不会将getObject()扩展为withD。我们只需要这样做,因为在使用配置之前,您将配置放到了一个单独的变量中,这时为时已晚,不能扩展类型。

最后

false

在这里,我们真的不知道booleanconst thirdObject = getObject({ withD: Math.random() < 0.5, other: "", some: "", values: "" }); // const thirdObject: SomeInterface | Pick<SomeInterface, "a" | "b" | "c"> thirdObject.d; // still error, since it might not be there 还是withD,因此编译器选择第三个调用签名,其中输出是一个并集。


哇!好的,希望对您有所帮助。祝你好运!

Link to code