TypeScript中带继承的命名事件/回调处理器实现问题
问题描述
我实现了一个NamedHandler类,通过泛型传入允许的事件/处理器名称列表,单类场景下使用正常。但尝试继承该类并扩展允许的处理器名称时,找不到可行的实现方式。
原类核心代码如下:
class NamedHandler<T extends string> { handlers: Record<T, (...args: any[]) => void> = {} as Record<T, (...args: any[]) => void>; on(event: T, handler: (...args: any[]) => void) { this.handlers[event] = handler; } emit(event: T, ...args: any[]) { this.handlers[event]?.(...args); } } // 期望扩展出支持'foo'|'bar'|'baz'的子类,但直接继承无法添加新事件名 class ExtendedHandler extends NamedHandler<'foo' | 'bar'> { // 如何添加'baz'作为允许的事件名? }
解决方案
以下是几种可行的实现方式:
1. 子类传入扩展后的联合泛型
直接在继承时将父类基础事件名与新事件名合并,这是最简单直接的固定扩展方案:
class ExtendedHandler extends NamedHandler<'foo' | 'bar' | 'baz'> { // 可添加子类专属逻辑 logExtension() { console.log("已扩展支持baz事件"); } } const handler = new ExtendedHandler(); handler.on('foo', () => {}); // ✅ 合法 handler.on('baz', () => {}); // ✅ 合法 handler.emit('bar'); // ✅ 合法
2. 子类支持动态扩展泛型
如果需要子类能灵活指定任意新增事件名,可让子类也接受泛型,并与父类基础事件名做联合:
class NamedHandler<T extends string> { handlers: Record<T, (...args: any[]) => void> = {} as Record<T, (...args: any[]) => void>; on(event: T, handler: (...args: any[]) => void) { this.handlers[event] = handler; } emit(event: T, ...args: any[]) { this.handlers[event]?.(...args); } } // 子类泛型默认never,代表不额外扩展事件 class ExtendedHandler<T extends string = never> extends NamedHandler<'foo' | 'bar' | T> { } // 使用时指定额外事件'baz' const handler = new ExtendedHandler<'baz'>(); handler.on('foo', () => {}); // ✅ 合法 handler.on('baz', () => {}); // ✅ 合法
3. 动态扩展实例的事件类型
如果希望实例化后能动态添加新事件名,可修改on方法的类型断言,自动扩展当前实例的事件类型:
class NamedHandler<T extends string = never> { handlers: Record<T, (...args: any[]) => void> = {} as Record<T, (...args: any[]) => void>; // 用asserts语法扩展实例的事件类型 on<K extends string>(event: K, handler: (...args: any[]) => void): asserts this is NamedHandler<T | K> { (this.handlers as Record<T | K, (...args: any[]) => void>)[event] = handler; } emit(event: T, ...args: any[]) { this.handlers[event]?.(...args); } } const handler = new NamedHandler<'foo'>(); handler.on('bar', () => {}); // 调用后,handler的事件类型自动扩展为'foo'|'bar' handler.emit('bar'); // ✅ 合法
说明
方案1适合固定扩展事件的场景,方案2适合需要灵活指定扩展事件的场景,方案3适合实例化后动态添加事件的场景,可根据实际需求选择。
内容的提问来源于stack exchange,提问作者Ingo Fischer
相关产品推荐
相关产品推荐

