为何带this参数的TypeScript函数属性会破坏父类等价性?
this类型在属性与函数类型中的赋值兼容性问题 问题场景
假设有抽象类Building及其子类Office、Home,原本子类实例可以正常赋值给Building类型变量:
const a = new Office(); const b = new Home(); // 无类型错误 let someBuilding: Building = a; someBuilding = b;
但为Building添加两个使用this类型的属性后,子类实例赋值给Building变量时出现报错:
abstract class Building { public particularProperty: this | null = this; public someFuncThatUsesType: ((a: this) => void) | null = null; } class Office extends Building {} class Home extends Building { public bedrooms = 0; } const a = new Office(); const b = new Home(); let someBuilding: Building = a; // 此处触发类型错误 someBuilding = b;
错误信息:
Type 'Home' is not assignable to type 'Building'.
Types of property 'someFuncThatUsesType' are incompatible.
Type '((a: Home) => void) | null' is not assignable to type '((a: Building) => void) | null'.
Type '(a: Home) => void' is not assignable to type '(a: Building) => void'.
Types of parameters 'a' and 'a' are incompatible.
Property 'bedrooms' is missing in type 'Building' but required in type 'Home'.
差异原因:协变与逆变的区别
particularProperty能正常工作的原因
particularProperty的类型是this | null,对于Home实例来说,这个属性的实际类型是Home | null。TypeScript中属性类型是协变的——子类型(Home)可以赋值给父类型(Building),所以Home | null可以兼容Building | null,不会触发错误。
someFuncThatUsesType报错的原因
someFuncThatUsesType的类型是((a: this) => void) | null,对于Home实例,这个属性的实际类型是((a: Home) => void) | null。而函数的参数类型是逆变的:如果要让(a: SubType) => void赋值给(a: SuperType) => void,需要SuperType是SubType的子类型(也就是逻辑反过来)。
这里Building是Home的父类型,所以(a: Home) => void无法赋值给(a: Building) => void——因为调用后者时可以传入任意Building子类(比如Office),但前者的参数要求必须是Home,类型不匹配,因此触发错误。
解决方案
方案1:将函数参数类型改为父类型Building
直接把this替换为Building,让函数参数统一接受父类型实例:
abstract class Building { public particularProperty: this | null = this; public someFuncThatUsesType: ((a: Building) => void) | null = null; }
方案2:将箭头函数属性改为类方法
TypeScript对类方法的参数类型采用双向协变(兼顾协变和逆变),可以绕过严格的逆变检查:
abstract class Building { public particularProperty: this | null = this; // 改为方法而非箭头函数属性 public someFuncThatUsesType?: (a: this) => void; }
方案3:使用泛型约束this类型
通过泛型将this的类型绑定到子类,明确类型关系:
abstract class Building<T extends Building<T>> { public particularProperty: T | null = this as T; public someFuncThatUsesType: ((a: T) => void) | null = null; } class Office extends Building<Office> {} class Home extends Building<Home> { public bedrooms = 0; }
此时someBuilding需要声明为Building<Building>或者具体子类的联合类型,可根据实际需求调整。
内容的提问来源于stack exchange,提问作者Simon Sarris

