我正在尝试使用泛型类型创建泛型函数,该泛型类型必须属于特定对象类型,不仅仅是任何对象。具体来说,我正在尝试围绕 AWS Lambda 处理程序函数创建一个包装器,并为请求和响应标头正文定义明确的类型。
这是迄今为止我能找到的最佳解决方案:
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
export type AsyncHandler = (event: APIGatewayProxyEvent, context: Context) => Promise<APIGatewayProxyResult>;
interface EmptyInterface {
_?: string;
}
export type RequestQueryParameters = EmptyInterface;
export type RequestHeaders = EmptyInterface;
export type RequestBody = EmptyInterface;
export interface Request<Q extends RequestQueryParameters, H extends RequestHeaders, B extends RequestBody> {
queryParameters: Q;
headers: H;
body?: B;
}
export type ResponseHeaders = EmptyInterface;
export type ResponseBody = EmptyInterface;
export interface Response<H extends ResponseHeaders, B extends ResponseBody> {
statusCode: number;
headers?: H;
body: B;
}
export function createHandler<Q, H, B, RH, RB>(main: (request: Request<Q, H, B>) => Promise<Response<RH, RB>>): AsyncHandler {
return async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
const response = await main({
queryParameters: (event.queryStringParameters as unknown) as Q,
headers: (event.headers as unknown) as H,
body: event.body ? (JSON.parse(event.body) as B) : undefined,
});
return {
statusCode: response.statusCode,
headers: response.headers as Record<string, string> | undefined,
body: JSON.stringify(response.body),
};
};
}
这是一个示例用法:
interface TestRequestQueryParameters extends RequestQueryParameters {
a: string;
}
interface TestRequestHeaders extends RequestHeaders {
b: string;
}
interface TestRequestBody extends RequestBody {
c: string;
d?: string;
e: number;
}
interface TestResponseHeaders extends ResponseHeaders {
f: string;
}
interface TestResponseBody extends ResponseBody {
g: string;
h?: string;
i: number;
}
const main = async (
request: Request<TestRequestQueryParameters, TestRequestHeaders, TestRequestBody>,
): Promise<Response<TestResponseHeaders, TestResponseBody>> => {
console.log(request.queryParameters.a);
console.log(request.headers.b);
console.log(request.body.c);
console.log(request.body.d);
console.log(request.body.e);
return {
statusCode: 200,
headers: {
f: 'test',
},
body: {
g: 'test',
h: 'test',
i: 100,
},
};
};
export const handler = createHandler(main);
这种方法的问题是:
_
属性。关于如何改进这一点有什么建议吗?是否有用于包装 AWS Lambda 处理程序的现有库?
编辑:在@jcalz 的帮助下,我使用Q extends Record<keyof Q, string | undefined>
改进了代码。但还有两点我想改进:
createHandler
的类型定义变得非常冗长。有没有办法简化它?b?: string
)。我可以通过执行 b: string | undefined
来解决这个问题。但是有没有办法b?: string
?这是更新后的示例代码:https://tsplay.dev/WkvlDm
答案 0 :(得分:1)
对于您希望允许任何内容的类型,只要它是对象而不是 primitive,您可以使用 the object
type。对于您的 B
类类型参数应该就是这种情况,您拥有的值(在底部的 tsplay 链接中)仅限制为 any | undefined
(即 any
,由方式)。
对于您希望确保将值限制为 string
或 undefined
并且您希望支持可选属性的类型,我会创建一个这样的类型别名:
type OptStrDict<T> = { [K in keyof T]?: string }
您将使用它来recursively constrain您的类型参数,例如 T extends OptStrDict<T>
。
所以你的 Request
界面变成了,例如:
export interface Request<
Q extends OptStrDict<Q>,
H extends OptStrDict<H>,
B extends object
> {
queryParameters: Q;
headers: H;
body?: B;
}
我将无需编写包含这些更改的完整代码版本,但您可以通过下面的 Playground 链接验证这是否有效。
请注意,重复使用 OptStrDict
类型别名将减少代码的冗长,尤其是将其缩短为 OSD
或其他内容时。如果您认为 createHandler()
的类型签名在此之后仍然过于冗长,我不知道如何最好地进行;务实地说,无论您做什么,拥有五个受约束的泛型类型参数都会占用一些空间。 ?♂️