我试图找到一种相对通用的方法来键入帖子正文和回复,以及它们的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自动推断帖子正文和返回类型时,我在第await fetch(url,行中得到了一个错误:

"PostTypeMapping"类型的键不能分配给"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};
  }
}

Why can url, which is typed as keyof PostTypeMapping, be a number?

我进行了进一步的研究,在大多数情况下,extends Record<string, {body: unknown, return: unknown}>似乎做了我想做的事情(迫使我向接口的所有条目添加一个body和返回类型),但允许数字键和字符串.为什么?这两种情况都是不允许的.

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

EDIT:

多亏了T.J.Crowder,这个问题的简化重现可以在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.这是按计划进行的.

但是对于像Record这样的mapped types,编译器不会立即以这种方式增加密钥.显然,Record<K, V>K的比例正确为contravariant是很重要的(根据comment in ms/TS#48269).

Record<string, any>毕竟是equivalent{[k: string]: any},因此我们有一个不一致性.TypeScript没有将一致性作为其最重要的设计目标;事实上,拥有一个可证明正确的类型系统是TypeScript的non-goal倍.从某种意义上说,生产力比正确性更重要.如果修复不一致性会使TypeScript对很多人来说都很烦人,那么最好不要使用不一致性.这显然是其中一种情况;根据same comment,这里的不一致性是无法消除的(大概是在不 destruct 语言中一些常用部分的情况下,比如数组的数字键),所以它不会消失.

哦,好吧!

Typescript相关问答推荐

TypScript手册中的never[]参数类型是什么意思?

如何为父类构造函数中的修饰属性赋值?

如何缩小变量访问的对象属性范围?

TypeScrip:基于参数的条件返回类型

具有泛型类方法的类方法修饰符

在MessageStore对象中实现条件类型时出现TypeScrip错误

获取一个TypeScrip对象,并将每个属性转换为独立对象的联合

(TypeScript)TypeError:无法读取未定义的属性(正在读取映射)"

如何过滤文字中的类对象联合类型?

Typescript,如何在通过属性存在过滤后重新分配类型?

打字错误TS2305:模块常量没有导出的成员

如何在具有嵌套数组的接口中允许新属性

如何在Deno中获取Base64图像的宽/高?

如何为带有参数的函数类型指定类型保护?

在REACT查询中获取未定义的isLoading态

是否可以将类型参数约束为不具有属性?

如何根据Typescript 中带有泛型的对象嵌套键数组获取数组或对象的正确类型?

Next 13 + React 18 createContext 类型的构建问题

递归地排除/省略两种类型之间的公共属性

根据索引将元组拆分为两个块