TypeScript实现含unknown泛型属性接口开启strictFunctionTypes报错如何解决
TypeScript strictFunctionTypes 下 EventEmitter 泛型不兼容问题解答
1. 报错背后的逻辑
strictFunctionTypes 开启后,函数参数会执行逆变校验:对于函数类型来说,如果类型A是类型B的父类,那么 (param: B) => void 是 (param: A) => void 的父类,类型继承方向和参数的继承方向相反。
对应你的场景:
InterfaceA定义的closed是EventEmitter<unknown>,它的emit方法可以接收任意类型的参数ComponentB里的closed是EventEmitter<CloseData>,它的emit方法只能接收CloseData类型的参数
如果允许ComponentB赋值给InterfaceA类型的变量,就会出现合法但不安全的操作:
const instance: InterfaceA = new ComponentB(); instance.closed.emit('任意不符合CloseData的内容'); // 按InterfaceA类型是合法的,但ComponentB实际不支持
这个就是TS抛出TS2416错误的根本原因,本质是类型系统在防止你写出运行时可能出错的代码。
2. 无any、不关闭严格模式的解决方案
方案1:给InterfaceA添加协变泛型参数(最优,改动最小)
如果允许调整InterfaceA的定义,只需要给它添加带默认值的协变泛型即可,完全不影响原有使用:
// out 关键字标记泛型为协变,允许子类型泛型赋值给父类型泛型 export interface InterfaceA<out T = unknown> { closed: EventEmitter<T>; }
ComponentB实现时指定泛型类型即可:
export class ComponentB implements InterfaceA<CloseData> { closed = new EventEmitter<CloseData>(); // ...其余逻辑完全不用改 }
这个方案完全符合类型安全要求,没有任何类型hack,默认泛型unknown也保证旧代码引用InterfaceA不用做任何改动。
方案2:安全类型断言(完全不用改InterfaceA和ComponentB的原有定义)
如果完全不能调整原有的接口和类定义,可以在需要把ComponentB实例赋值给InterfaceA类型的地方做两层类型断言,全程不需要用到any:
const componentBInstance = new ComponentB(); const interfaceAInstance: InterfaceA = componentBInstance as unknown as InterfaceA;
注意这个方案的前提是:你保证业务逻辑中不会通过InterfaceA类型的引用去调用closed.emit方法,只会用来做事件订阅。订阅逻辑本身是安全的,因为订阅unknown类型的事件可以兼容任意类型的返回值。
方案3:适配器包装(零侵入原有代码,完全类型安全)
如果连类型断言都不想用,可以写一个轻量适配器把ComponentB实例转换成符合InterfaceA的类型:
function adaptToInterfaceA(component: ComponentB): InterfaceA { return { ...component, closed: component.closed as EventEmitter<unknown> } }
这个方案和断言的安全性前提一致,只是把类型转换收敛到了统一的适配器函数里,更便于维护。
以上所有方案都不需要用到
any类型,也不需要关闭strictFunctionTypes校验,完全符合严格模式的代码规范。
内容的提问来源于stack exchange,提问作者Rin
相关产品推荐
相关产品推荐

