英文:
Passing unknown as generic in typescript
问题 {#heading}
我正在使用这个npm包,在其中,除其他内容外,他们使用以下类型(这是该包中类型的简化版本):
export interface APIGatewayProxyStructuredResultV2 {
statusCode?: number;
headers?: {
[header: string]: boolean | number | string;
};
body?: string;
isBase64Encoded?: boolean;
cookies?: string[];
}
export interface APIGatewayProxyEvent {
version: string;
routeKey: string;
rawPath: string;
body?: string;
}
type ProxyIntegrationBody<T = unknown> = {
body?: T;
};
type APIGatewayEventRequestContext = {
key: string;
}
export type ProxyIntegrationEvent<T = unknown> = Omit<APIGatewayProxyEvent, 'body'> & ProxyIntegrationBody<T>
export type ProxyIntegrationResult = Omit<APIGatewayProxyStructuredResultV2, 'statusCode'> & {
statusCode?: APIGatewayProxyStructuredResultV2['statusCode'];
};
export interface ProxyIntegrationRoute {
path: string
method: 'GET' | 'POST' | 'OPTIONS' | 'PUT';
action: (
request: ProxyIntegrationEvent<unknown>,
context: APIGatewayEventRequestContext
) => ProxyIntegrationResult | Promise<ProxyIntegrationResult> | string | Promise<string>
}
export interface ProxyIntegrationConfig {
routes: ProxyIntegrationRoute[];
}
然后在我的代码中,我尝试使用这些类型,如下所示:
const handler: ProxyIntegrationConfig = {
routes: [
{
path: '/playedGames',
method: 'POST',
action: async (request: ProxyIntegrationEvent, context) => {
return { statusCode: 200, body: '' };
}
},
{
path: '/playedGames',
method: 'POST',
action: async (request: ProxyIntegrationEvent<{ name: string, numbers: string[] }>, context) => {
return { statusCode: 200, body: '' };
}
},
]
};
如果我理解泛型的使用正确,我应该能够通过泛型向ProxyIntegrationEvent
传递自定义类型,然而,在路由数组的第二个对象中,我得到了以下 TypeScript 错误:
Type '(request: ProxyIntegrationEvent<{ name: string; numbers: string[]; }>, context: APIGatewayEventRequestContext) => Promise<{ statusCode: number; body: string; }>' is not assignable to type '(request: ProxyIntegrationEvent<unknown>, context: APIGatewayEventRequestContext) => string | ProxyIntegrationResult | Promise<...> | Promise<...>'.
Types of parameters 'request' and 'request' are incompatible.
Type 'ProxyIntegrationEvent<unknown>' is not assignable to type 'ProxyIntegrationEvent<{ name: string; numbers: string[]; }>'.
Type 'ProxyIntegrationEvent<unknown>' is not assignable to type 'ProxyIntegrationBody<{ name: string; numbers: string[]; }>'.
Types of property 'body' are incompatible.
Type 'unknown' is not assignable to type '{ name: string; numbers: string[]; } | undefined'.ts(2322)
Untitled-1(29, 5): The expected type comes from property 'action' which is declared here on type 'ProxyIntegrationRoute'
如果我移除泛型并简单地使用(request: ProxyIntegrationEvent, context)
,就像我在路由数组的第一个对象中所做的那样,问题会得到解决,但这样做毫无意义,因为它不会为request
参数提供任何类型信息。
我是不是在使用这些类型时出错了,或者这是包中的某种类型错误?我考虑要分支该项目,因此如果需要的话我可以修复它。 英文:
I'm using this npm package where, among other stuff, they use the following types (this is a simplified version of the types in that package):
export interface APIGatewayProxyStructuredResultV2 {
statusCode?: number;
headers?: {
[header: string]: boolean | number | string;
};
body?: string;
isBase64Encoded?: boolean;
cookies?: string[];
}
export interface APIGatewayProxyEvent {
version: string;
routeKey: string;
rawPath: string;
body?: string;
}
type ProxyIntegrationBody<T = unknown> = {
body?: T;
};
type APIGatewayEventRequestContext = {
key: string;
}
export type ProxyIntegrationEvent<T = unknown> = Omit<APIGatewayProxyEvent, 'body'> & ProxyIntegrationBody<T>
export type ProxyIntegrationResult = Omit<APIGatewayProxyStructuredResultV2, 'statusCode'> & {
statusCode?: APIGatewayProxyStructuredResultV2['statusCode'];
};
export interface ProxyIntegrationRoute {
path: string
method: 'GET' | 'POST' | 'OPTIONS' | 'PUT';
action: (
request: ProxyIntegrationEvent<unknown>,
context: APIGatewayEventRequestContext
) => ProxyIntegrationResult | Promise<ProxyIntegrationResult> | string | Promise<string>
}
export interface ProxyIntegrationConfig {
routes: ProxyIntegrationRoute[];
}
Then in my code, I'm trying to use the types as follows
const handler: ProxyIntegrationConfig = {
routes: [
{
path: '/playedGames',
method: 'POST',
action: async (request: ProxyIntegrationEvent, context) => {
return { statusCode: 200, body: '' };
}
},
{
path: '/playedGames',
method: 'POST',
action: async (request: ProxyIntegrationEvent<{ name: string, numbers: string[] }>, context) => {
return { statusCode: 200, body: '' };
}
},
]
};
If I understand generics correctly, I should be able to pass a custom type to ProxyIntegrationEvent
via generics, however, I'm getting the following typescript error in the second object of the routes array:
Type '(request: ProxyIntegrationEvent<{ name: string; numbers: string[]; }>, context: APIGatewayEventRequestContext) => Promise<{ statusCode: number; body: string; }>' is not assignable to type '(request: ProxyIntegrationEvent<unknown>, context: APIGatewayEventRequestContext) => string | ProxyIntegrationResult | Promise<...> | Promise<...>'.
Types of parameters 'request' and 'request' are incompatible.
Type 'ProxyIntegrationEvent<unknown>' is not assignable to type 'ProxyIntegrationEvent<{ name: string; numbers: string[]; }>'.
Type 'ProxyIntegrationEvent<unknown>' is not assignable to type 'ProxyIntegrationBody<{ name: string; numbers: string[]; }>'.
Types of property 'body' are incompatible.
Type 'unknown' is not assignable to type '{ name: string; numbers: string[]; } | undefined'.ts(2322)
Untitled-1(29, 5): The expected type comes from property 'action' which is declared here on type 'ProxyIntegrationRoute'
If I remove the generic and simply do (request: ProxyIntegrationEvent, context)
, as I have in the first object of the routes array, it fixes the problem but that defeats the whole purpose because it doesn't give me any typing for the request
parameter.
Am I using these types incorrectly or is this some typing bug in the package? I'm thinking of forking that project anyway so I could fix it if needed.
答案1 {#1}
得分: 1
如果您希望ProxyIntegrationConfig
只是允许 request
回调参数的类型比unknown
更窄,那么您可以随时在各种定义中将unknown
更改为any
,如下所示:
interface ProxyIntegrationRoute {
path: string
method: 'GET' | 'POST' | 'OPTIONS' | 'PUT';
action: (
request: ProxyIntegrationEvent<any>, // 编辑
context: APIGatewayEventRequestContext
) => ProxyIntegrationResult | Promise<ProxyIntegrationResult> | string | Promise<string>
}
但这会降低类型安全性,直到当前被拒绝的不安全操作也被接受,尽管它们是不安全的:
const handler: ProxyIntegrationConfig = {
routes: [
{
path: '/playedGames',
method: 'POST',
action: async (request: ProxyIntegrationEvent, context) => {
return { statusCode: 200, body: '' };
}
},
{
path: '/playedGames',
method: 'POST',
action: async (request:
ProxyIntegrationEvent<{ name: string, numbers: string[] }>, // 可行
context) => {
return { statusCode: 200, body: '' };
}
},
]
};
declare const a: APIGatewayEventRequestContext
handler.routes[1].action(
{ rawPath: '', routeKey: "", version: "", body: { name: "a", numbers: [] } },
a); // 可行
handler.routes[1].action(
{ rawPath: '', routeKey: "", version: "", body: { oops: 123 } },
a); // 可行?
另一方面,如果您希望ProxyIntegrationConfig
既允许又跟踪这些更具体的类型,以便验证类型安全性,那么它需要编码到类型中。
这意味着ProxyIntegrationConfig
可能应该是泛型的,泛型类型参数为ProxyIntegrationEvent<>
类型的request
参数的数组类型,该类型用于routes
数组中每个元素的request
参数,如下所示:
interface ProxyIntegrationConfig<T extends unknown[] = unknown[]> {
routes: { [I in keyof T]: ProxyIntegrationRoute<T[I]> };
}
这是一种映射数组类型,将T
中的元素数组/元组转换为routes
中元素的相应数组/元组。这也意味着ProxyIntegrationRoute
需要是泛型的:
interface ProxyIntegrationRoute<T = unknown> {
path: string
method: 'GET' | 'POST' | 'OPTIONS' | 'PUT';
action: (
request: ProxyIntegrationEvent<T>,
context: APIGatewayEventRequestContext
) => ProxyIntegrationResult | Promise<ProxyIntegrationResult> | string | Promise<string>
}
现在我们可以为您的handler
提供准确的类型:
const handler: ProxyIntegrationConfig<[unknown, { name: string, numbers: string[] }]> = {
routes: [
{
path: '/playedGames',
method: 'POST',
action: async (request, context) => {
return { statusCode: 200, body: '' };
}
},
{
path: '/playedGames',
method: 'POST',
action: async (request, context) => {
return { statusCode: 200, body: '' };
}
},
]
};
请注意,这实际上并不更难编写,因为您不再需要对request
参数进行注释。现在我们可以获得强类型的好处:
handler.routes[1].action(
{ rawPath: '', routeKey: "", version: "", body: { name: "a", numbers: [] } },
a); // 可行
handler.routes[1].action(
{ rawPath: '', routeKey: "", version: "", body: { oops: 123 } }, // 错误!
a);
请注意,也许any
真的是可接受的,因为您可以使用满足运算符来避免对handler
进行任何注释。但所有这些都极大地取决于对handler
的用途,这可能最终超出了此处的范围。
If you'd like ProxyIntegrationConfig
to just allow the request
callback param to be of a type narrower than unknown
, then you can always just change unknown
to any
in various definitions like:
interface ProxyIntegrationRoute {
path: string
method: 'GET' | 'POST' | 'OPTIONS' | 'PUT';
action: (
request: ProxyIntegrationEvent<any>, // edit
context: APIGatewayEventRequestContext
) => ProxyIntegrationResult | Promise<ProxyIntegrationResult> | string | Promise<string>
}
But this loosens the type safety until things which are currently rejected for being unsafe are accepted despite being unsafe:
const handler: ProxyIntegrationConfig = {
routes: [
{
path: '/playedGames',
method: 'POST',
action: async (request: ProxyIntegrationEvent, context) => {
return { statusCode: 200, body: '' };
}
},
{
path: '/playedGames',
method: 'POST',
action: async (request:
ProxyIntegrationEvent<{ name: string, numbers: string[] }>, // okay
context) => {
return { statusCode: 200, body: '' };
}
},
]
};
`declare const a: APIGatewayEventRequestContext
handler.routes[1].action(
{ rawPath: '', routeKey: "", version: "", body: { name: "a", numbers: [] } },
a); // okay
handler.routes[1].action(
{ rawPath: '', routeKey: "", version: "", body: { oops: 123 } },
a); // okay?
`
On the other hand if you want ProxyIntegrationConfig
to both allow and keep track of these more specific types so that it can verify type safety, then it needs to be encoded into the type.
That means ProxyIntegrationConfig
should maybe be generic in the type of the array of type arguments to the ProxyIntegrationEvent<>
type for the request
parameters in each element of the routes
array. Like this:
interface ProxyIntegrationConfig<T extends unknown[] = unknown[]> {
routes: { [I in keyof T]: ProxyIntegrationRoute<T[I]> };
}
That's a mapped array type, transforming the array/tuple of elements in T
to a corresponding array/tuple of elements of routes
. That also means ProxyIntegrationRoute
needs to be generic:
interface ProxyIntegrationRoute<T = unknown> {
path: string
method: 'GET' | 'POST' | 'OPTIONS' | 'PUT';
action: (
request: ProxyIntegrationEvent<T>,
context: APIGatewayEventRequestContext
) => ProxyIntegrationResult | Promise<ProxyIntegrationResult> | string | Promise<string>
}
So now we can give your handler
an accurate type:
const handler: ProxyIntegrationConfig<[unknown, { name: string, numbers: string[] }]> = {
routes: [
{
path: '/playedGames',
method: 'POST',
action: async (request, context) => {
return { statusCode: 200, body: '' };
}
},
{
path: '/playedGames',
method: 'POST',
action: async (request, context) => {
return { statusCode: 200, body: '' };
}
},
]
};
And note that this isn't really any harder to write, since you don't have to annotate the request
parameter anymore. And now we get the benefits of strong types:
handler.routes[1].action(
{ rawPath: '', routeKey: "", version: "", body: { name: "a", numbers: [] } },
a); // okay
`handler.routes[1].action(
{ rawPath: '', routeKey: "", version: "", body: { oops: 123 } }, // error!
a);
`
Note that maybe any
really is acceptable, since you can use the satisfies
operator to avoid annotation of handler
at all. But all of that depends strongly on the use case for what you do with handler
, which is probably ultimately out of scope here.