如何在TypeScript枚举中实现泛型以优化键盘类代码
解决键盘按键状态管理中的代码重复问题
背景与初始困境
最近我在实现keydown事件的按键状态存储与校验功能时,为了区分不同类型的按键,定义了五个枚举标志:Alphabet、Modifier、Number、Navigation、Special,并把这些逻辑封装在了一个Keyboard类里。
这个类包含了每个枚举对应的get/set属性、一个set(key: string)方法,以及一系列isAlphabet()、isModifier()这类的判断方法——但很快我就发现,代码里充满了重复逻辑,完全违反了**DRY(Don't Repeat Yourself)**原则。比如每个枚举类型的getter/setter几乎是一模一样的模板,isXXX方法也只是换了个枚举类型的判断条件,维护起来特别麻烦。
泛型优化的尝试与卡点
我一开始想通过泛型来精简这些重复代码,但在把枚举类型和泛型语法结合的时候遇到了不少问题:比如怎么让泛型正确约束枚举的类型,怎么动态生成不同枚举对应的属性和方法,这些都没找到顺畅的实现方式,卡了好一阵子。
重构后的精简方案
后来我调整了思路,重新设计了这套逻辑,最终的重构方案完美解决了代码重复问题:
- 先定义一个统一的
KeyType枚举来标识所有按键类型,替代原来的五个独立枚举:
enum KeyType { Alphabet, Modifier, Number, Navigation, Special }
- 用
Keys枚举存储所有按键值,配合一个KeyMap数组来映射按键与对应类型的关系:
enum Keys { A = 'a', Ctrl = 'Control', // ... 其他按键枚举值 } const KeyMap: { key: Keys; type: KeyType }[] = [ { key: Keys.A, type: KeyType.Alphabet }, { key: Keys.Ctrl, type: KeyType.Modifier }, // ... 其他按键与类型的映射关系 ];
- 用泛型
IFlag接口约束状态标志的结构,再封装一个通用的FlagContainer类来处理状态的设置与判断:
interface IFlag<T> { [key: string]: T; } class FlagContainer<T extends KeyType> { private flags: IFlag<boolean> = {}; set(key: string, type: T) { const match = KeyMap.find(item => item.key === key && item.type === type); if (match) { this.flags[key] = true; } } is(type: T, key: string): boolean { const match = KeyMap.find(item => item.key === key && item.type === type); return !!match && this.flags[key] === true; } getFlags(type: T): IFlag<boolean> { return Object.fromEntries( Object.entries(this.flags).filter(([key]) => KeyMap.some(item => item.key === key && item.type === type) ) ); } }
- 最后只需要实例化
FlagContainer并关联KeyType,就能得到精简后的Keyboard对象——原来的重复代码全部被通用逻辑替代了。
这种重构方式不仅消除了大量冗余代码,还让按键类型的扩展变得更简单:如果要新增一种按键类型,只需要在KeyType里加枚举值,补充KeyMap的映射即可,不需要修改容器类的核心逻辑。
内容的提问来源于stack exchange,提问作者zwoolli
相关产品推荐
相关产品推荐

