泛型无法与父类协同?TypeScript代码问题求助
问题背景
我定义了ParentSchema类存储数据库公共字段,供其他Schema类继承;同时定义了接收Model作为泛型的ModelService类,包含update等通用数据库操作方法;另外还有MongoJob类,用于向ModelService方法传递所需参数,ModelService.update()方法会将自身的泛型传递给MongoJob。
相关代码:
/* ParentSchema to hold common properties */ export class ParentSchema { id!: string; } /* MongoJob to hold properties to run job */ export interface MongoJob<T extends ParentSchema> { /** * primary key value of the model */ id?: number | string; /** * body object used for create or update */ body?: Partial<T>; } export class MongoJob<T extends ParentSchema> { constructor(job: MongoJob<T>) { this.id = job.id; this.body = job.body || {}; } } /* ModelService to hold common methods */ export class ModelService<M extends ParentSchema> { constructor() { } async update(job: MongoJob<M>): Promise<any> { try { console.log(job); const response = {}; // call db functions return response; } catch (error) { return { error }; } } } /* Country model */ export class Country extends ParentSchema { name!: string; } /* Calling functions using Country model */ const service = new ModelService<Country>(); /* Working */ service.update( new MongoJob<Country>({ id: 1, body: { name: 'Demo' }, }) ); /* Working */ service.update( new MongoJob({ id: 1, body: {}, }) ); /* Not Working */ service.update( new MongoJob({ id: 1, body: { name: 'Demo' }, // Type '{ name: string; }' is not assignable to type 'Partial<ParentSchema>'. }) );
问题点
实例化ModelService<Country>后调用update方法时,若不给MongoJob指定<Country>泛型且传入非空body,会出现类型错误——Type '{ name: string; }' is not assignable to type 'Partial<ParentSchema>',但ModelService已指定泛型为Country。
原因与解决方案
问题根源
TypeScript的泛型推导是从内到外进行的:当你调用service.update(new MongoJob({...}))时,TS会先推导MongoJob的泛型参数,而不是先从update方法的参数类型反向推导。因为MongoJob的泛型约束是T extends ParentSchema,如果不手动指定,TS会默认推导出最宽泛的ParentSchema,导致body被限制为Partial<ParentSchema>,无法接受Country的额外字段。
另外,原代码中同时定义了interface MongoJob和class MongoJob,属于冗余定义,会造成潜在的命名冲突问题,建议移除。
修复方案1:让ModelService封装MongoJob实例化(推荐)
修改update方法,让它直接接收MongoJob的初始化参数,内部完成实例化。这样TS会从ModelService的泛型M反向推导参数类型,无需调用方手动指定MongoJob的泛型:
/* 先移除冗余的MongoJob interface定义 */ export class MongoJob<T extends ParentSchema> { id?: number | string; body?: Partial<T>; constructor(job: { id?: number | string; body?: Partial<T> }) { this.id = job.id; this.body = job.body || {}; } } /* 修改ModelService的update方法 */ export class ModelService<M extends ParentSchema> { constructor() { } async update(jobParams: { id?: number | string; body?: Partial<M> }): Promise<any> { const job = new MongoJob<M>(jobParams); try { console.log(job); const response = {}; // call db functions return response; } catch (error) { return { error }; } } } // 调用时直接传参数对象即可 service.update({ id: 1, body: { name: 'Demo' }, });
修复方案2:增强MongoJob的泛型推导能力
调整MongoJob的定义,让它能从传入的body自动推导泛型类型,同时保留与ParentSchema的约束:
export class MongoJob<T extends ParentSchema = ParentSchema> { id?: number | string; body?: Partial<T>; constructor(job: { id?: number | string; body?: Partial<T> }) { this.id = job.id; this.body = job.body || {}; } } // 调用时TS会从body字段推导泛型为Country service.update( new MongoJob({ id: 1, body: { name: 'Demo' }, }) );
这种方式适合需要直接操作MongoJob实例的场景,但复杂场景下仍可能需要手动指定泛型。
内容的提问来源于stack exchange,提问作者Rijin K

