为什么 interface extends Record<string, ...> 允许数字键?
Why does interface extends Record<string, ...> allow numeric keys?
我正在尝试找到一种相对通用的方式来输入 POST 正文以及我返回的响应与他们的 API 路由(在 nextjs 应用程序中)。
为此,我希望编译器强制我向所有 API 路由添加一个 body
类型和一个 return
类型,我通过以下接口实现了这一点:
export interface PostTypeMapping extends Record<string, {body: unknown, return: unknown}> {
"/api/taskCompletions": {body: PostCompletionBody, return: void},
"/api/task": {body: PostTaskBody, return: void},
}
到目前为止一切顺利。我可以像这样在我的 API 路线中使用这种类型:
async (req, res: NextApiResponse<PostTypeMapping["api/task"]["return"]>) => {
//...
}
但是当我尝试编写一个自动从 URL 推断 POST 主体和 return 类型的包装器时,我在 [=17] 行中得到一个错误=]:
Argument of type 'keyof PostTypeMapping' is not assignable to parameter of type 'RequestInfo'.
Type 'number' is not assignable to type 'RequestInfo'
export async function fetchPost<T extends keyof PostTypeMapping>(url: T, body: PostTypeMapping[T]["body"]): Promise<PostTypeMapping[T]["return"]> {
try {
const res = await fetch(url, { // <- The error above occurs here
method: "POST",
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
},
});
if(res.status === 201 || res.status === 204){
return;
}
return res.json();
} catch (err: any){
return {message: "Fehler: " + err.message};
}
}
为什么输入为keyof PostTypeMapping
的url
可以是数字?
我进行了进一步调查,在大多数情况下,extends Record<string, {body: unknown, return: unknown}>
确实符合我的要求(迫使我向界面的所有条目添加正文和 return 类型),但允许数字键和字符串。为什么?不允许的两种情况都是好的。
export interface PostTypeMapping extends Record<string, {body: unknown, return: unknown}> {
"/api/taskCompletions": {body: PostCompletionBody, return: void},
"/api/task": {body: PostTaskBody, return: void},
1: {body: void, return: void}, // why is this legal? 1 is not a string
2: "asd" // not allowed -> Property '2' of type '"asd"' is not assignable to 'string' index type '{ body: unknown; return: unknown; }'
"asd": "asd" // not allowed -> Property '"asd"' of type '"asd"' is not assignable to 'string' index type '{ body: unknown; return: unknown; }
}
编辑:
由于 T.J,可以在 https://tsplay.dev/Nr5X2w 找到该问题的简化重现。克劳德
有关此问题的权威答案,请参阅 microsoft/TypeScript#48269。
字符串 index signatures 始终允许使用数字键,因为 JavaScript 中的非 symbol
键总是首先被强制转换为字符串。所以“number
”键实际上应该更像“数字字符串”,但 TypeScript 允许您将它们视为数字以支持用数字索引到数组中。
在 TypeScript 2.9 之前,keyof {[k: string]: any}
应该是 string
。但是 TypeScript 2.9 引入了 support for number
and symbol
properties with keyof
。此更改的一部分是 keyof X
,其中 X
具有字符串索引签名,现在包括 number
。所以 keyof {[k: string]: any}
是 string | number
。这是按预期工作的。
但无论如何 mapped types like Record
, the compiler does not immediately augment the keys this way. Apparently it is important that Record<K, V>
be properly in K
(according to the comment in ms/TS#48269。
但Record<string, any>
毕竟等价于{[k: string]: any}
,因此我们有矛盾。 TypeScript 并未将一致性作为其最重要的设计目标;确实是non-goal of TypeScript to have a provably correct type system. Productivity is, in some sense, more important than correctness. If fixing an inconsistency would make TypeScript very annoying to use for a lot of people, then it's better to leave the inconsistency. And this is apparently one of those situations; according to the same comment,这里的不一致无法消除(大概不会破坏语言的某些oft-used部分,例如数组的数字键),所以它仍然存在。
好吧!
我正在尝试找到一种相对通用的方式来输入 POST 正文以及我返回的响应与他们的 API 路由(在 nextjs 应用程序中)。
为此,我希望编译器强制我向所有 API 路由添加一个 body
类型和一个 return
类型,我通过以下接口实现了这一点:
export interface PostTypeMapping extends Record<string, {body: unknown, return: unknown}> {
"/api/taskCompletions": {body: PostCompletionBody, return: void},
"/api/task": {body: PostTaskBody, return: void},
}
到目前为止一切顺利。我可以像这样在我的 API 路线中使用这种类型:
async (req, res: NextApiResponse<PostTypeMapping["api/task"]["return"]>) => {
//...
}
但是当我尝试编写一个自动从 URL 推断 POST 主体和 return 类型的包装器时,我在 [=17] 行中得到一个错误=]:
Argument of type 'keyof PostTypeMapping' is not assignable to parameter of type 'RequestInfo'. Type 'number' is not assignable to type 'RequestInfo'
export async function fetchPost<T extends keyof PostTypeMapping>(url: T, body: PostTypeMapping[T]["body"]): Promise<PostTypeMapping[T]["return"]> {
try {
const res = await fetch(url, { // <- The error above occurs here
method: "POST",
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
},
});
if(res.status === 201 || res.status === 204){
return;
}
return res.json();
} catch (err: any){
return {message: "Fehler: " + err.message};
}
}
为什么输入为keyof PostTypeMapping
的url
可以是数字?
我进行了进一步调查,在大多数情况下,extends Record<string, {body: unknown, return: unknown}>
确实符合我的要求(迫使我向界面的所有条目添加正文和 return 类型),但允许数字键和字符串。为什么?不允许的两种情况都是好的。
export interface PostTypeMapping extends Record<string, {body: unknown, return: unknown}> {
"/api/taskCompletions": {body: PostCompletionBody, return: void},
"/api/task": {body: PostTaskBody, return: void},
1: {body: void, return: void}, // why is this legal? 1 is not a string
2: "asd" // not allowed -> Property '2' of type '"asd"' is not assignable to 'string' index type '{ body: unknown; return: unknown; }'
"asd": "asd" // not allowed -> Property '"asd"' of type '"asd"' is not assignable to 'string' index type '{ body: unknown; return: unknown; }
}
编辑:
由于 T.J,可以在 https://tsplay.dev/Nr5X2w 找到该问题的简化重现。克劳德
有关此问题的权威答案,请参阅 microsoft/TypeScript#48269。
字符串 index signatures 始终允许使用数字键,因为 JavaScript 中的非 symbol
键总是首先被强制转换为字符串。所以“number
”键实际上应该更像“数字字符串”,但 TypeScript 允许您将它们视为数字以支持用数字索引到数组中。
在 TypeScript 2.9 之前,keyof {[k: string]: any}
应该是 string
。但是 TypeScript 2.9 引入了 support for number
and symbol
properties with keyof
。此更改的一部分是 keyof X
,其中 X
具有字符串索引签名,现在包括 number
。所以 keyof {[k: string]: any}
是 string | number
。这是按预期工作的。
但无论如何 mapped types like Record
, the compiler does not immediately augment the keys this way. Apparently it is important that Record<K, V>
be properly K
(according to the comment in ms/TS#48269。
但Record<string, any>
毕竟等价于{[k: string]: any}
,因此我们有矛盾。 TypeScript 并未将一致性作为其最重要的设计目标;确实是non-goal of TypeScript to have a provably correct type system. Productivity is, in some sense, more important than correctness. If fixing an inconsistency would make TypeScript very annoying to use for a lot of people, then it's better to leave the inconsistency. And this is apparently one of those situations; according to the same comment,这里的不一致无法消除(大概不会破坏语言的某些oft-used部分,例如数组的数字键),所以它仍然存在。
好吧!