为什么 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 PostTypeMappingurl可以是数字?

我进行了进一步调查,在大多数情况下,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; }
}

typescript playground

编辑:

由于 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部分,例如数组的数字键),所以它仍然存在。

好吧!