我有这样一个类(“简化”):
class MyDb {
create(user: {name: string, id: number}) {
//
}
delete(id: number) {
//
}
read(id: number) {
//
}
}
const db = new MyDb()
我得到了这样一个函数:
function processRequest <Method extends keyof MyDb, Data extends Parameters<MyDb[Method]>> (
method: Method,
data: Data
) {
db[method](data) // here goes error. Argument of type 'number | { name: string; id: number; }' is not assignable to parameter of type '{ name: string; id: number; } & number'.
}
我试过使用Parameters<...>[0]
或db[method](...data)
,但它对我没有帮助。另外,对我来说有趣的是,我对泛型的想法是,当我调用那个函数时,“类型”会被推断出来,但在这种情况下,它在编写过程中不知何故会中断。
操场
2条答案
按热度按时间jm2pwxwz1#
TypeScript不能像
processRequest()
主体中的Parameters<MyDb[K]>
那样真正对genericconditional types执行抽象推理(因为K
或Method
是泛型类型参数,而Parameters
实用程序是用条件类型实现的)。这样的类型对于类型检查器来说基本上是不透明的,因此为了评估它,db[method]
的类型从泛型MyDb[K]
扩展到函数MyDb[keyof MyDb]
的并集。通常,你不能安全地调用一个函数的联合,除非你传递给它一个参数的交集(参见发行说明,了解对调用函数联合的支持)。基本上,db[method]
类型和data
类型之间的通用相关性已经丢失。如果你不关心编译器理解相关性的能力,你可以只使用类型Assert,然后继续你的一天:
但是如果你想让编译器遵循你的推理,那么推荐的方法,如microsoft/TypeScript#47109中所描述的,是重构你的类型操作,这样它们就可以用一些“基本”Map类型来表示,泛型indexed accessesMap到该基本类型,mapped typesMap到该基本类型。这将最终使用与您所拥有的类型等效的类型,但它们将以一种让编译器“看到”预期关系的方式表示。
这里有一个办法。首先,让我们将基类型定义为
Db
方法的参数。这可以通过编程实现:然后可以写
processRequest
,使得data
直接是DbParams[K]
:重要的是,我们已经将
Db
类型的db
赋值给了Map类型为{ [P in keyof MyDb]: (arg: DbParams[P]) => void }
的变量_db
。这相当于Db
,但它被显式地写为基础DbParams
类型的Map类型。赋值成功是因为它实际上不是泛型(Db
和Map类型都是不依赖于K
的特定类型)。现在通话成功了
_db[method]
的类型被显式地视为(arg: DbParams[K]) => void
。这是一个接受DbParams[P]
类型参数的单个调用签名。由于这正是data
的类型,因此调用_db[method](data)
成功。同样,此版本与您的版本之间的唯一区别是类型的表示形式,如microsoft/TypeScript#47109中所述的泛型/Map/索引版本。如果这不是必须的,那就太好了,但是现在,如果你想避免类型Assert或类似的东西,这是你必须做的。
Playground链接到代码
fumotvh32#
我只添加了callDbMethod,用于安全地调用方法。您的实现可能已经获得了类型化参数。技巧是将as与**infinite类型的