TypeScript子类调用父类回调方法传入子类参数报错如何解决?
问题原因
这个报错是TypeScript的函数参数逆变规则导致的:
- 函数的参数类型是逆变的,也就是如果
B是A的子类,那么(ev: B) => void不是(ev: A) => void的子类型,反过来才成立。 - 根本原因是父类
A的log方法定义中,允许给回调传任意A类型的实例(比如其他继承自A的子类C的实例),如果你传入接收B类型的回调,万一log内传了C的实例给回调,回调中访问ev.addr就会出现运行时错误,TS会提前拦截这个风险。
解决方案
以下方案均不需要对回调参数做强制类型转换:
方案1:使用多态this类型(最简便,无额外开发成本)
直接修改父类A的log方法定义,把回调参数类型指定为this,类型会自动匹配当前调用实例的实际类型:
abstract class A { name: string; // 回调参数用this类型,自动跟随实例类型变化 log(callback: (ev: this) => void) {} constructor(name: string) { this.name = name; } } class B extends A { addr: string; constructor(addr: string, name: string) { super(name); this.addr = addr; } } let b = new B("1", "2"); // 回调参数自动推导为B类型,无报错 b.log((ev) => { console.log(ev.addr) // 可正常访问子类特有属性 });
该方案不需要修改子类逻辑,父类修改成本极低,是最推荐的解法。
方案2:给父类增加泛型参数(适用复杂继承场景)
如果需要更灵活的类型控制,可以给父类声明泛型,子类继承时指定自身为泛型参数:
// 泛型T约束为继承自A的类型 abstract class A<T extends A> { name: string; log(callback: (ev: T) => void) {} constructor(name: string) { this.name = name; } } // 子类继承时指定泛型为自身类型B class B extends A<B> { addr: string; constructor(addr: string, name: string) { super(name); this.addr = addr; } } let b = new B("1", "2"); b.log((ev: B) => {}); // 无报错
方案3:关闭函数严格类型检查(不推荐,仅做参考)
如果你的项目可以确认所有log方法调用时传的都是对应子类的实例,也可以在tsconfig.json中把strictFunctionTypes设为false,关闭函数参数逆变检查。该方案会牺牲类型安全性,不建议生产环境使用。
内容的提问来源于stack exchange,提问作者Ondolin
相关产品推荐
相关产品推荐

