我正在用类型脚本编写一个函数,它将接受一个请求类型的参数,然后返回一个 struct 为变量的对象.我写的函数是这样的:

export async function getFormData<T>(req: Request): Promise<T> {
  let data = {} as T
  const formdata = await req.formData();
  formdata.forEach((value, key) => {
    data[key] = value; // <- This line gives the error
  });
  return data;
}

我的推理是,当我运行函数getFormData时,我会像这样运行它 const res = await getFormData<myInterface>(req);个 这样,Res就会有合适的myInterface类型.但Typescript 总是给我错误

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'unknown'.
  No index signature with a parameter of type 'string' was found on type 'unknown'

Tl;dr:如何编写一个函数,该函数将返回在我调用函数时指定的类型的对象

这个(Generic Object type in typescript)问题在某种程度上解决了问题,但我得到的所有东西都是‘any’类型

推荐答案

问题是,FormData表示一组键-值对,其中键是一些未知的string,值是一些未知的FormDataEntryValue,即defined作为eitherstringFile.绝对不能保证当您将它们组合到一个对象中时,该对象是否与您的T类型匹配.编译器错误基本上是警告您,它不能安全地将value赋给data[key],因为它既不知道key是有效的键T,也不知道value是该键的适当值(如果它确实存在于T上).

事实上,使用像<T>(req: Request) => Promise<T>这样的调用签名可以安全地实现impossible.有人可以用same req变量同时调用getFormData<MyInterface>(req)getFormData<AnotherInferface>(req).根据调用签名,第一个调用将返回Promise<MyInterface>,第二个调用将返回Promise<AnotherInterface>.但这是不可能的;在运行时,这两个数字都只有getFormData(req).这些预测最多只有一个是正确的.

因此,如果您真的想要像这样保留调用签名,您实际上是依赖于调用者通过某个带外信息源知道为给定的req指定哪个T.而呼叫者基本上只有asserting,这T的正确率.

因此,您不妨在getFormData()的主体内使用type assertions.类型断言不是类型安全的,但它们是将此类带外类型信息传递给编译器的一种方式.这里有一种方法:

async function getFormData<T>(req: Request): Promise<T> {
    let data: Record<string, FormDataEntryValue> = {};
    (await req.formData()).forEach((value, key) => {
        data[key] = value;
    });
    return data as T;
}

制造data唯一安全的东西是Record<string, FormDataEntryValue>(使用Record实用程序类型).这是一个带有index signature的类型,这意味着"data可能有任何string个键,而它的所有属性都是FormDataEntryValue类型."一旦您进行了更改,return之前的代码就会编译而不会出错...毕竟,将FormDataEntryValue赋给类型为FormDataEntryValue的属性是安全的.

return行现在具有类型断言return data as T;.这是您告诉编译器调用方指定的任何T都将与data的类型匹配的部分.再说一次,这不是类型安全...或者至少您正在将类型安全的负担从编译器转移到您自己身上.如果有人在他们的代码中调用getFormData<TheWrongInterface>(req),那么在运行时可能会出现问题,因为编译器只是认为data将是TheWrongInterface类型的.这种错误信念的任何负面后果都不能归咎于编译器,它会警告您存在问题,直到您断言它符合要求.

这就对了.该代码编译时没有错误,并且它may在运行时表现良好,假设调用者知道返回数据的实际形状,并且他们在调用时正确地指定了它.或者,它可能会在运行时爆炸.当被问及哪一种情况更有可能发生,以及如何让事情变得更安全时,这是超出了问题的范围.

Playground link to code

Typescript相关问答推荐

从接口创建类型安全的嵌套 struct

如何键入函数以只接受映射到其他一些特定类型的参数类型?

无法正确推断嵌套泛型?

如何调整对象 struct 复杂的脚本函数的泛型类型?

在Typescript 中,有没有`index=unfined的速记?未定义:某个数组[索引]`?

如何在深度嵌套的Reaction路由对象中隐藏父级?

如何在react组件中使用doctype和html标签?

类型TTextKey不能用于索引类型 ;TOption

转换器不需要的类型交集

为什么Typescript只在调用方提供显式泛型类型参数时推断此函数的返回类型?

->;Boolean类型的键不能分配给类型Never

与toast.error一起react 中的Async TUNK错误消息

如何从一个泛型类型推断多个类型

Route.ts文件中未导出HTTP方法

有没有一种简单的方法可以访问Keyboard Shortcuts JSON文件中列出的每个命令的显示名称(标题)?

两个接口的TypeScript并集,其中一个是可选的

如何使用 Angular 确定给定值是否与应用程序中特定单选按钮的值匹配?

具有枚举泛型类型的 Typescript 对象数组

如果父级被删除或删除,如何自动删除子级?

TypeScript 中的通用函数包装和类型推断