TypeScript如何兼容子类不同构造签名并正常访问静态属性
TypeScript 子类自定义构造函数同时保留静态属性类型访问方案
问题场景
初始定义基类Action及多个继承子类,通过typeof Action作为类型约束访问类的静态属性:
class Action { public static DEPENDENCIES: (typeof Action)[] = []; public static MIN_USES: number | null = null; public static MAX_USES: number | null = null; } class SomeAction extends Action { public static DEPENDENCIES = [SomeOtherAction]; public static MIN_USES = null; public static MAX_USES = 1; } class SomeOtherAction extends Action { public static DEPENDENCIES = []; public static MIN_USES = 1; public static MAX_USES = 1; } class Turn { public can(A: typeof Action) { console.log(A.MIN_USES); } } const turn = new Turn(); turn.can(SomeOtherAction); // 正常输出1
当给子类(比如SomeOtherAction)定义带2个入参的构造函数时,会抛出静态侧类型不兼容错误:
Class static side 'typeof SomeOtherAction' incorrectly extends base class static side 'typeof Action'.
如果把类型约束替换为纯构造函数类型new(...args: any[]) => Action,虽然能解决构造签名兼容问题,但访问静态属性时会丢失类型,抛出:
Property 'MIN_USES' does not exist on type 'new (...args: any[]) => Action'.
解决方案
核心思路是自定义一个同时包含放宽版构造签名、以及所有需要访问的静态属性契约的接口,替代原生typeof 基类的类型约束:
- 首先定义静态侧契约接口,构造函数签名放宽为支持任意入参,同时声明所有公共静态属性的类型:
interface ActionClass { new (...args: any[]): Action; DEPENDENCIES: ActionClass[]; MIN_USES: number | null; MAX_USES: number | null; }
- 将代码中所有原来用
typeof Action做类类型约束的位置,替换为自定义的ActionClass接口:
class Action { // 替换原typeof Action[]类型 public static DEPENDENCIES: ActionClass[] = []; public static MIN_USES: number | null = null; public static MAX_USES: number | null = null; } class Turn { // 替换参数的typeof Action类型 public can(A: ActionClass) { console.log(A.MIN_USES); console.log(A.MAX_USES); console.log(A.DEPENDENCIES); } }
- 此时给子类添加任意参数的构造函数都不会触发类型错误,同时静态属性访问依然有完整的类型校验和提示:
class SomeOtherAction extends Action { public static DEPENDENCIES: ActionClass[] = []; public static MIN_USES = 1; public static MAX_USES = 1; // 自定义2个入参的构造函数,无类型报错 constructor(paramA: string, paramB: number) { super(); // 自定义构造逻辑 } } const turn = new Turn(); turn.can(SomeOtherAction); // 正常运行,输出1,类型校验完全通过
原理说明
- 原生
typeof Action类型会默认把基类的构造函数签名(无参数)作为静态侧类型的一部分,子类构造函数入参和基类不一致时,就会判定静态侧类型不兼容。 - 自定义的
ActionClass接口手动拆分了约束规则:构造函数部分不限制入参数量和类型,只要求返回Action实例;静态属性部分明确声明类型规则,既兼容不同入参的子类构造函数,又保留了静态属性的类型检查能力。 - 如果后续有新增的公共静态属性,只需要在
ActionClass接口中补充对应类型定义,所有子类的静态属性都会自动被校验是否符合契约。
内容的提问来源于stack exchange,提问作者Andries
相关产品推荐
相关产品推荐

