如何为继承抽象类的对象正确指定TypeScript类型?
问题场景
场景1:标注Record<string, Layer<any>>后丢失子类类型
当给layers标注Record<string, Layer<any>>类型时,无法直接调用子类的test方法,TS抛出错误:
TS2339: Property 'test' does not exist on type 'Layer'.
代码示例:
export class GameScene extends Layer<GameSceneEvents> { test() { } } layers: Record<string, Layer<any>> = { gameScene: new GameScene(), winnScene: new WinnScene(), }; // 报错:test不存在于Layer类型 layers.gameScene.test();
场景2:移除类型标注后遍历报错
移除手动类型标注后,直接访问layers.gameScene.test()正常,但使用Object.keys遍历时,TS抛出索引类型不匹配错误:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ gameScene: GameScene; winnScene: WinnScene; }'. No index signature with a parameter of type 'string' was found on type '{ gameScene: GameScene; winnScene: WinnScene; }'.
代码示例:
const layers = { gameScene: new GameScene(), winnScene: new WinnScene(), }; // 正常运行 layers.gameScene.test(); // 遍历报错 Object.keys(layers).forEach((key:string)=>{ layers[key].test() });
完整代码:
export abstract class Layer<T extends string> { container = []; eventEmitter = []; } type WinnSceneEvents = "test"; export class WinnScene extends Layer<GameSceneEvents> { test() { console.log("test"); } } type GameSceneEvents = "test"; export class GameScene extends Layer<GameSceneEvents> { test() { console.log("test"); } } const layers = { gameScene: new GameScene(), winnScene: new WinnScene(), }; layers.gameScene.test(); Object.keys(layers).forEach((key:string)=>{ layers[key].test() });
解决方案
1. 强制子类实现test方法(保证类型安全)
修改抽象类Layer,添加抽象方法test,强制所有子类必须实现该方法,从根源上避免调用test时的类型错误:
export abstract class Layer<T extends string> { container: any[] = []; eventEmitter: any[] = []; // 抽象方法,子类必须实现 abstract test(): void; }
2. 正确指定layers的类型
不要使用Record<string, Layer<any>>(会丢失子类类型信息),有两种可选方式:
方式一:利用TypeScript自动类型推导
直接不手动标注类型,让TS自动推断layers的具体类型:
const layers = { gameScene: new GameScene(), winnScene: new WinnScene(), };
此时TS会推导layers的类型为{ gameScene: GameScene; winnScene: WinnScene; },直接访问子类方法完全类型安全。
方式二:显式定义类型别名
如果需要显式标注类型,可定义包含具体键和对应类型的类型别名:
type LayersMap = { gameScene: GameScene; winnScene: WinnScene; }; const layers: LayersMap = { gameScene: new GameScene(), winnScene: new WinnScene(), };
3. 类型安全的遍历方案
Object.keys返回的是string[],而layers的键是字面量类型("gameScene" | "winnScene"),需要将key转换为keyof类型,有两种实现方式:
方案一:类型断言
直接将遍历的key断言为keyof typeof layers:
Object.keys(layers).forEach((key) => { layers[key as keyof typeof layers].test(); });
方案二:自定义类型安全遍历函数
封装一个函数,将Object.keys的返回值转换为keyof T类型,避免直接断言:
function iterateLayers<T extends Record<string, Layer<any>>>(obj: T) { return Object.keys(obj) as Array<keyof T>; } // 类型安全的遍历 iterateLayers(layers).forEach(key => { layers[key].test(); });
总结
- 通过抽象类强制子类实现公共方法,保证类型约束的严谨性。
- 避免使用
Record<string, Layer<any>>这类宽泛的类型,利用TS自动推导或显式定义具体类型,保留子类类型信息。 - 遍历对象时,通过类型断言或自定义函数解决
Object.keys的类型不匹配问题,同时维持类型安全。
内容的提问来源于stack exchange,提问作者Serge Esmanovich

