抱歉,这个标题让人摸不着头绪!我尽量在这里说清楚。给定以下接口(由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;
};
然而,这不会正常工作,我得到以下错误:
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>> => {
//...
};
现在类型P
和M
绑定在一起了。
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
类型不需要任何类型参数,而我的RequestParams
和ResponseType
类型只需要一个类型参数。
这里是一个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
这是一个更新过的操场。