将输入参数约束为某些泛型类型的超类型,并使结果类型为相同类型

时间:2019-05-10 09:29:10

标签: typescript generics

我正在尝试创建一种类型安全的GraphQL数据获取方法。我使用服务器上生成的类型,并希望选择在这些类型上定义的属性的子集作为结果类型。

从服务器生成的代码如下

export interface Dream {
    id: number;
    title?: string;
}

export interface IQuery {
    dream(id: number): Dream | Promise<Dream>;
}

检索数据的功能:

function doQuery<
    QName extends keyof IQuery,
    QParams extends Parameters<IQuery[QName]>,
    R extends Partial<ReturnType<IQuery[QName]>>
>(queryName: QName, params: QParams, resultSelector: R): R {
    return {} as R; // stub
}

还有一个实用程序类,可以在运行时使用“类型”

class types {
    static number: number;
    static string: string;
    static boolean: boolean;
}

例如,当调用函数时

const result = doQuery("dream", [2], {
    title: types.string
});

结果类型正确推断为

const result: {
    title: string;
}

当我打电话时

const result = doQuery("dream", [2], {
    nonExisting: types.string,
});

编译器抛出Object literal may only specify known properties, and 'nonExisting' does not exist in type 'RecursivePartial<Dream>,这正是我想要的。

但是,当使用调用该函数时

const result = doQuery("dream", [2], {
    id: types.number,
    nonExisting: types.string,
});

编译器说可以,我不希望这样。我希望编译器以与以前相同的方式抛出。

结果类型应始终与resultSelector类型相同。

到目前为止,在我已经尝试过的代码中,尚未处理查询结果为Dream | Promise<Dream>的可能性。我相信条件类型应该可以做到这一点。

“部分”部分也是我能想到的最好的部分,但实际上应该是ReturnType<IQuery[QName]> extends R而不是相反的方式,我不知道该如何在Typescript中表达。

是否有可能在Typescript的当前水平上实现我正在寻找的约束?如果是这样,怎么办?如果没有,为什么不呢?

谢谢。

1 个答案:

答案 0 :(得分:0)

我设法使其适用于

type Awaited<T> = T extends { then(onfulfilled: (value: infer U) => any): any }
    ? U
    : T extends { then(...args: any[]): any }
    ? never
    : T;

type Subset<S, F> = { [key in keyof S]: key extends keyof F ? F[key] : never };

function doQuery<
    QName extends keyof IQuery,
    QParams extends Parameters<IQuery[QName]>,
    R extends Subset<R, Awaited<ReturnType<IQuery[QName]>>>
>(queryName: QName, params: QParams, resultSelector: R): R {
    return {} as R;
}