类型 'any[]' 不可分配给类型“[id: string]”

Type 'any[]' is not assignable to type '[id: string]'

我正在尝试创建一个通用 API 类型,它的属性是 return 承诺的功能。

export type API = {
  [key: string]: <Params extends any[], Response>(...params: Params) => Promise<Response>,
}

export interface UserResponse {
  data: User
}

export interface User {
  id: string;
  name: string;
}

export const api: API = {
  get: (id: User['id']): Promise<UserResponse> => Promise.resolve({
    data: {
      id,
      name: 'name'
    }
  })
};

这给了我关于 get 的错误:

Type '(id: User['id']) => Promise<UserResponse>' is not assignable to type '<Params extends any[], Response>(...params: Params) => Promise<Response>'.
  Types of parameters 'id' and 'params' are incompatible.
    Type 'Params' is not assignable to type '[id: string]'.
      Type 'any[]' is not assignable to type '[id: string]'.
        Target requires 1 element(s) but source may have fewer.

如何让泛型接受 id: string 作为 API 方法的参数?

Playground

这是因为您错误地使用了模板文字。最简单(也是最“错误”的答案)如下...这会破坏很多用例,并且您的通用...

export type API = {
  [key: string]: (...params: any[]) => Promise<Response>,
}

取而代之的是让我们走你需要这个通用的路线:

让我们问问自己 API 类型实际上要求什么,它将其所有 key/value 对定义为具有任意数量的参数,这在技术上是正确的,但会暴露 API const 不正确,允许你做这样的事情

API.get() //<-- No error thrown here, since it uses the API typing correctly.
API.get(true, false, 'foo', 'bar') //<-- This also is allowed

因此我们会抛出这个错误 Target requires 1 element(s) but source may have fewer. 基本上概述了在 API 的导出端有人可能会错误地使用它。

第一个选项:显式键入 API 的所有成员。这是确保 API.

形状的首选且最安全的选项
export type API = {
  get: (id: string) => Promise<Response>
  //set: (id: string) => Promise<Response>
  //...
}

第二种选择:让 TS 推断您的 API。对于不变性,分配 as const assertion.

const api = {
  get: (id: User['id']): Promise<Response> => Promise.resolve({
    data: {
      id,
      name: 'name'
    }
  }),
} //as const
// {
//   get: (id: User['id']) => Promise<Response>;
// }

第三个选项:创建一个包装函数,它将推断一个 return 值,这是 TS 中的一个常见技巧,滥用 function inferencing 来确定基于赋值的类型。如果你想使用 TS 强制 API 具有特定类型,这是最好的选择,尤其是。如果多个开发人员将接触此代码(但不想明确键入 API)。就易读性而言,这也是最糟糕的选择,混淆了那些可能不熟悉 TS 的人的类型,并且可能使追踪 api 是什么变得更加困难。

const returnAPI = <
  T extends Record<string, (...args: any[]) => Promise<Response>>
>(o: T): {
  [K in keyof T]: T[K]
} => o

const api = returnAPI({
  get: (id: User['id']): Promise<Response> => Promise.resolve({
    data: {
      id,
      name: 'name'
    }
  }),
})

我们使用extends关键字来表示T必须是某种形状,在这种情况下,由扩展string值的键组成,并且它们的值必须是一个函数,它总是returns Promise<Response>,这个可以根据需要修改。

查看 Playground 以找到最适合您的选项。