TypeScript抽象类与接口方法返回自身实现类实例类型的解决方案
解决方案
有两种常用方案可以实现这个需求,优先推荐使用TypeScript内置的多态this类型,实现成本最低也最符合语义。
方案1:使用多态this类型
这个类型会自动指向当前调用方法的实例的实际类型,不需要额外声明泛型,改法非常简单:
首先修改基接口和基类定义:
export interface IPersonViewModel { name: string; // 把返回值从IPersonViewModel改为this setName(value: string): this; } export abstract class PersonViewModel implements IPersonViewModel { constructor(name: string) { this.name = name; } public name: string; public setName(value: string): this { // 用类型断言告知TS返回值符合当前实例类型,扩展运算符已拷贝所有实例属性,断言是安全的 const vm = { ...this, name: value } as this; return vm; } }
派生接口和派生类无需做任何修改,此时在IEmployeeViewModel实例上调用setName方法,返回值会自动被推导为IEmployeeViewModel类型,测试效果如下:
const emp = new EmployeeViewModel('engineer', '张三'); const newEmp = emp.setName('李四'); // newEmp类型自动识别为IEmployeeViewModel,可正常调用派生类方法 const newEmp2 = newEmp.setRole('senior engineer'); // 无类型报错
方案2:使用泛型约束
如果你的场景需要更灵活的类型控制,也可以给基接口和基类增加泛型参数:
// 基接口增加泛型参数T,默认约束为IPersonViewModel类型 export interface IPersonViewModel<T extends IPersonViewModel = IPersonViewModel> { name: string; setName(value: string): T; } // 基类同步增加泛型参数 export abstract class PersonViewModel<T extends IPersonViewModel = IPersonViewModel> implements IPersonViewModel<T> { constructor(name: string) { this.name = name; } public name: string; public setName(value: string): T { const vm = { ...this, name: value } as T; return vm; } }
派生时将自身类型作为泛型参数传入即可:
// 派生接口传入自身类型作为泛型参数 export interface IEmployeeViewModel extends IPersonViewModel<IEmployeeViewModel> { role: string; setRole(value: string): IEmployeeViewModel; } // 派生类同步传入自身类型 export class EmployeeViewModel extends PersonViewModel<IEmployeeViewModel> implements IEmployeeViewModel { constructor(role: string, name: string) { super(name); this.role = role; } public role: string; public setRole(value: string): IEmployeeViewModel { const vm = { ...this, role: value }; return vm; } }
该方案的使用效果和方案1完全一致,适合需要对返回类型做额外定制的场景。
内容的提问来源于stack exchange,提问作者user8607541
相关产品推荐
相关产品推荐

