动态引用嵌套接口类型的泛型TypeScript类型



抱歉,这个标题让人摸不着头绪!我尽量在这里说清楚。给定以下接口(由openapi-typescript作为API定义生成):

TypeScript playground看看这个是怎么回事

export interface paths {
'/v1/some-path/:id': {
get: {
parameters: {
query: {
id: number;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
post: {
parameters: {
body: {
name: string;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
};
}

上面的接口paths将有许多由字符串标识的路径,每个路径都有一些可用的方法,然后定义参数和响应类型。

我试图写一个通用的apiCall函数,这样给定的路径和方法知道所需参数的类型,和返回类型。

这是我目前为止写的:

type Path = keyof paths;
type PathMethods<P extends Path> = keyof paths[P];
type RequestParams<P extends Path, M extends PathMethods<P>> =
paths[P][M]['parameters'];
type ResponseType<P extends Path, M extends PathMethods<P>> =
paths[P][M]['responses'][200]['schema'];
export const apiCall = (
path: Path,
method: PathMethods<typeof path>,
params: RequestParams<typeof path, typeof method>
): Promise<ResponseType<typeof path, typeof method>> => {
const url = path;
console.log('params', params);
// method & url are 
return fetch(url, { method }) as any;
};

然而,这不会正常工作,我得到以下错误:

  1. paths[P][M]['parameters']['path']->Type '"parameters"' cannot be used to index type 'paths[P][M]'即使它确实工作(如果我做type test = RequestParams<'/v1/some-path/:id', 'get'>然后test显示正确的类型)

知道如何实现这一点吗?

解决方案

经过几次尝试,这就是我找到的解决方案。

首先,我使用条件类型定义RequestParams:

type RequestParams<P extends Path, M extends PathMethods<P>> = 
"parameters" extends keyof paths[P][M] 
? paths[P][M]["parameters"]
: undefined;

因为typescript动态地推断出path的类型,键parameters可能不存在,所以我们不能使用它。条件类型检查此特定情况。

对于ResponseType(它将更加详细)也可以这样做,以访问typescript抱怨的属性。

然后,我更新了函数apiCall的签名:
export const apiCall = <P extends Path, M extends PathMethods<P>>(
path: P,
method: M,
params: RequestParams<P, M>
): Promise<ResponseType<P, M>> => {
//...
};

现在类型PM绑定在一起了。

奖金最后,如果不需要参数,我再次使用条件类型使参数params成为可选的:
export const apiCall = <P extends Path, M extends PathMethods<P>>(
path: P,
method: M,
...params: RequestParams<P, M> extends undefined ? []: [RequestParams<P, M>]
): Promise<ResponseType<P, M>> => {
//...
};

这是一个带有解决方案的工作类型脚本游乐场。我添加了一个没有参数的方法delete,只是为了测试最终的用例。

<标题>来源
  • https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
  • https://stackoverflow.com/a/52318137/6325822

编辑

这里是更新后的typescript playground,没有错误。

此外,我看到Alessio的解决方案只适用于一条路径,这有点限制。我建议的那个没有错误,并且适用于任何数量的路径。

我通过链接到他的TypeScript游乐场来检查bababoo的解决方案。在第57行,ResponseType类型给出了以下错误:

Type '"responses"' cannot be used to index type 'paths[P][M]'.(2536)
Type '200' cannot be used to index type 'paths[P][M]["responses"]'.(2536)
Type '"schema"' cannot be used to index type 'paths[P][M]["responses"][200]'.(2536)

我从那个解决方案开始做了一些工作,获得了没有错误所需的功能,并使用了稍微简单的类型定义,这需要更少的类型参数。特别是,我的PathMethod类型不需要任何类型参数,而我的RequestParamsResponseType类型只需要一个类型参数。

这里是一个TypeScript playground,包含了完整的解决方案。

应captain-yossarian的要求,以下是完整的解决方案:

export interface paths {
'/v1/some-path/:id': {
get: {
parameters: {
query: {
id: number;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
post: {
parameters: {
body: {
name: string;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
delete: {
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
};
}
type Path = keyof paths;
type PathMethod = keyof paths[Path];
type RequestParams<T extends PathMethod> = paths[Path][T] extends {parameters: any} ? paths[Path][T]['parameters'] : undefined;
type ResponseType<T extends PathMethod> = paths[Path][T] extends {responses: {200: {schema: {[x: string]: any}}}} ? keyof paths[Path][T]['responses'][200]['schema'] : undefined;
export const apiCall = <P extends Path, M extends PathMethod>(
path: P,
method: M,
...params: RequestParams<M> extends undefined ? [] : [RequestParams<M>]
): Promise<ResponseType<M>> => {
const url = path;
console.log('params', params);
return fetch(url, { method }) as any;
};

更新:

在评论中,aurbano指出,我的解决方案只有在paths只有一个键时才有效。下面是一个更新后的解决方案,它适用于两个不同的路径。

export interface paths {
'/v1/some-path/:id': {
get: {
parameters: {
query: {
id: number;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
post: {
parameters: {
body: {
name: string;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
delete: {
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
};
'/v2/some-path/:id': {
patch: {
parameters: {
path: {
id: number;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
};
}
type Path = keyof paths;
type PathMethod<T extends Path> = keyof paths[T];
type RequestParams<P extends Path, M extends PathMethod<P>> = paths[P][M] extends {parameters: any} ? paths[P][M]['parameters'] : undefined;
type ResponseType<P extends Path, M extends PathMethod<P>> = paths[P][M] extends {responses: {200: {schema: {[x: string]: any}}}} ? keyof paths[P][M]['responses'][200]['schema'] : undefined;
export const apiCall = <P extends Path, M extends PathMethod<P>>(
path: P,
method: M,
...params: RequestParams<P, M> extends undefined ? [] : [RequestParams<P, M>]
): Promise<ResponseType<P, M>> => {
const url = path;
console.log('params', params);
return fetch(url, { method: method as string }) as any;
};
apiCall("/v1/some-path/:id", "get", {
header: {},
query: {
id: 1
}
}); // Passes -> OK
apiCall("/v2/some-path/:id", "get", {
header: {},
query: {
id: 1
}
}); // Type error -> OK
apiCall("/v2/some-path/:id", "patch", {
header: {},
query: {
id: 1
}
}); // Type error -> OK
apiCall("/v2/some-path/:id", "patch", {
header: {},
path: {
id: 1,
}
}); // Passes -> OK
apiCall("/v1/some-path/:id", "get", {
header: {},
query: {
id: 'ee'
}
}); // Type error -> OK
apiCall("/v1/some-path/:id", "get", {
query: {
id: 1
}
}); // Type error -> OK
apiCall("/v1/some-path/:id", "get"); // Type error -> OK
apiCall("/v1/some-path/:id", 'delete'); // Passes -> OK
apiCall("/v1/some-path/:id", "delete", {
header: {},
query: {
id: 1
}
}); // Type error -> OK

这是一个更新过的操场。

相关内容

  • 没有找到相关文章

最新更新