我有这样一个班级("简化"):

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),但这对我没有帮助. 此外,对我来说有趣的是,我对泛型的 idea 是,当我调用该函数时,会推断出"类型",但在这种情况下,它在编写过程中不知何故会中断.

Playground

推荐答案

processRequest()的主体内,TypeScrip不能真正执行像Parameters<MyDb[K]>这样的generic conditional types的抽象推理(因为KMethod是泛型类型参数,而the Parameters utility是用条件类型实现的.这样的类型对于类型判断器来说基本上是不透明的,因此为了判断它,db[method]的类型从泛型MyDb[K]扩展到函数MyDb[keyof MyDb]union.通常,除非向其传递intersection个参数(请参见the release notes for the introduction of support for calling a union of functions),否则不能安全地调用函数的联合.基本上,db[method]类型和data类型之间的类属correlation已经丢失.

如果您不关心编译器理解相关性的能力,您可以只使用type assertion,然后继续您的工作:

(db[method] as (arg: typeof data) => void)(data); // okay

但是,如果您希望编译器遵循您的推理,那么推荐的方法,如第microsoft/TypeScript#47109节所述,是重构您的类型操作,以便它们以某种"基本"映射类型的形式表示,泛型indexed accesses映射到该基本类型,mapped types表示在该基本类型上.这最终将使用与您所拥有的相同的类型,但它们将以一种让编译器"看到"预期关系的方式来表示.

有一种方法可以做到这点.首先,让我们将基类型定义为Db个方法的参数.这可以通过编程方式完成:

type DbParams = { [K in keyof MyDb]: Parameters<MyDb[K]>[0] }        

/* type DbParams = {
    create: {
        name: string;
        id: number;
    };
    delete: number;
    read: number;
} */

然后可以写入processRequest,这样data就直接是DbParams[K]:

function processRequest<K extends keyof MyDb>(
    method: K,
    data: DbParams[K]
) {
    const _db: { [P in keyof MyDb]: (arg: DbParams[P]) => void } = db;
    _db[method](data); // okay
}

重要的是,我们将类型Dbdb赋给了映射类型{ [P in keyof MyDb]: (arg: DbParams[P]) => void }的变量_db.这等同于Db,但它被显式地编写为覆盖基本DbParams类型的映射类型.赋值之所以成功,是因为它实际上不是泛型(Db和映射类型都是不依赖于K的特定类型).

现在,这通电话起作用了._db[method]的类型被明确地视为(arg: DbParams[K]) => void.这是接受类型DbParams[P]的参数的单个调用签名.由于这正是data的类型,因此调用_db[method](data)成功.

同样,此版本和您的版本之间的唯一区别是类型在泛型/映射/索引版本方面的表示形式,如微软/TypeScrip#47109中所述.如果这不是必需的,那就太好了,但是现在,如果您想要避免类型断言或类似的东西,就必须这样做.

Playground link to code

Typescript相关问答推荐

创建一个通用上下文,允许正确推断其中的子项

如何在方法中定义TypeScript返回类型,以根据参数化类型推断变量的存在?

类型安全JSON解析

当我点击外部按钮时,如何打开Html Select 选项菜单?

如何在TypeScrip面向对象程序设计中用方法/属性有条件地扩展类

如何正确地对类型脚本泛型进行限制

被推断为原始类型的交集的扩充类型的交集

ANGLING v17 CLI默认设置为独立:延迟加载是否仍需要@NgModule进行路由?

vue-tsc失败,错误引用项目可能无法禁用vue项目上的emit

基元类型按引用传递的解决方法

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

Typescript 类型守卫一个类泛型?

如何在使用条件类型时使用void

棱角分明-什么是...接口类型<;T>;扩展函数{new(...args:any[]):t;}...卑鄙?

如何扩展RxJS?

字幕子集一个元组的多个元素

来自枚举的<;复选框和组件列表

如何在TypeScript中将元组与泛型一起使用?

如何通过nx找到所有受影响的项目?

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