TypeScript传this作为属性接口泛型参数报类型不兼容错误如何解决
问题原因
该类型错误由TypeScript的类型方差校验规则导致:
- 定义的
BaseEvents<me>接口中,泛型参数me仅出现在回调函数的参数位置,TypeScript对未显式标记方差的泛型默认按不变处理,既不允许协变赋值,也不允许逆变赋值。 - 基类
Base的listeners属性使用了多态this类型,当Child继承Base时,Child实例的listeners类型自动推导为BaseEvents<Child>,要求render回调必须接收Child类型(包含子类独有的foo方法)作为参数。 create函数的泛型约束为T extends typeof Base,TypeScript会校验Child构造器的返回值(即Child实例)是否满足Base类型约束:Base类型要求listeners可以兼容BaseEvents<Base>(即回调仅需接收纯Base实例),但默认不变的泛型规则不允许BaseEvents<Child>与BaseEvents<Base>互相赋值,因此抛出类型错误。
解决方案
方案1:标记泛型为逆变(推荐,改动最小)
由于泛型me仅出现在回调参数这个逆变位置,可以使用in关键字显式标记泛型为逆变类型参数,TypeScript会按逆变规则校验类型兼容性,既保留完整的类型推导能力,又能解决报错:
// 用in标记泛型me为逆变类型参数 interface BaseEvents<in me extends Base> { render: (me: me) => void } class Base { public listeners?: BaseEvents<this> } class Child extends Base { foo() {} } function create<T extends typeof Base>(cls: T) { return new cls; } const c = create(Child); // 自动推导c为Child类型,c.listeners.render的参数类型也会自动推导为Child c.listeners?.render(c);
该方案不需要修改子类和工厂函数的写法,事件回调的参数类型会自动跟随子类的类型推导,完全符合需求。
方案2:基类显式声明泛型参数(兼容旧版TS)
如果使用的TypeScript版本不支持泛型方差标注(TS 4.7以下版本),可以给基类增加泛型参数,子类继承时显式传入自身类型:
interface BaseEvents<me extends Base> { render: (me: me) => void } // 基类增加泛型参数,默认值设为Base class Base<T extends Base = Base> { public listeners?: BaseEvents<T> } // 子类继承时将自身作为泛型参数传入 class Child extends Base<Child> { foo() {} } // 工厂函数增加返回值类型推导 function create<T extends typeof Base>(cls: T): InstanceType<T> { return new cls; } const c = create(Child); c.listeners?.render(c);
该方案的缺点是子类继承时需要手动传递自身类型,写法稍显冗余。
内容的提问来源于stack exchange,提问作者Merijn Schering
相关产品推荐
相关产品推荐

