首页 > 解决方案 > 将输入参数约束为某个泛型类型的超类型,并使结果类型为同一类型

问题描述

我正在尝试制作一种类型安全的 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>尚未处理查询结果的可能性。我相信条件类型应该可以做到这一点。

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

是否可以在当前的 Typescript 技术中实现我正在寻找的约束?如果是这样,怎么做?如果不是,为什么不呢?

提前致谢。

标签: typescriptgenerics

解决方案


我设法使它与

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

推荐阅读