如何在TypeScript中无需传入实例存储类型参数的键?
问题:TypeScript泛型类中获取泛型参数的键并存储到Map中
我尝试编写如下TypeScript代码,希望在MyClass类中无需传入实例的情况下,获取泛型参数T的键并存储在Map中:
type TKeyed = { [key: string]: any }; class MyClass<T extends TKeyed> { private keysMap: Map<string, any>; constructor() { const keys = Object.keys({} as T); // 尝试获取T的键 this.keysMap = new Map<string, any>(); for (const key of keys) { this.keysMap.set(key, null); // 将键存入Map } } getKeysMap(): Map<string, any> { return this.keysMap; } } interface MyType extends TKeyed { prop1: string; prop2: number; } const myInstance = new MyClass<MyType>(); const keysMap = myInstance.getKeysMap(); console.log(keysMap); // 预期输出: Map { 'prop1' => null, 'prop2' => null }
预期输出是包含prop1和prop2的Map,但实际结果是Map (0) {}。我还尝试用const keys = Reflect.ownKeys({} as T);提取键,同样无效。请问该如何解决?
原因分析
TypeScript的类型系统是编译时生效的,泛型参数T在编译完成后会被完全擦除,运行时根本不存在这个类型信息。你写的{} as T只是编译时的类型断言,运行时它就是一个空对象,所以Object.keys和Reflect.ownKeys都只能拿到空数组,最终Map自然是空的。
解决方法
要在运行时获取泛型类型T的键,必须提供一个运行时存在的T类型对象作为参考(因为类型本身在运行时不存在)。这里有两种常用的实现方式:
方式一:构造函数传入样本对象
修改MyClass的构造函数,让它接收一个T类型的样本对象(值可以随便填,只要键正确就行):
type TKeyed = { [key: string]: any }; class MyClass<T extends TKeyed> { private keysMap: Map<string, any>; // 接收T类型的样本对象 constructor(sample: T) { const keys = Object.keys(sample); this.keysMap = new Map<string, any>(); for (const key of keys) { this.keysMap.set(key, null); } } getKeysMap(): Map<string, any> { return this.keysMap; } } interface MyType extends TKeyed { prop1: string; prop2: number; } // 传入包含所有键的样本对象,值可以是对应类型的默认值 const myInstance = new MyClass<MyType>({ prop1: '', prop2: 0 }); const keysMap = myInstance.getKeysMap(); console.log(keysMap); // 输出: Map(2) {'prop1' => null, 'prop2' => null}
这种方式简单直接,样本对象的键就是泛型T的键,运行时可以通过Object.keys正确获取。
方式二:利用类的原型(仅适用于类类型的T)
如果你的泛型参数T是类而不是接口,可以通过传入类的构造函数,从原型上获取键(但接口没有原型,所以这种方式不适用你的场景):
class MyTypeClass { prop1: string = ''; prop2: number = 0; } class MyClass<T extends object> { private keysMap: Map<string, any>; constructor(cls: new () => T) { const prototype = cls.prototype; const keys = Object.getOwnPropertyNames(prototype).filter(key => key !== 'constructor'); this.keysMap = new Map<string, any>(); for (const key of keys) { this.keysMap.set(key, null); } } getKeysMap(): Map<string, any> { return this.keysMap; } } const myInstance = new MyClass(MyTypeClass); console.log(myInstance.getKeysMap()); // 输出: Map(2) {'prop1' => null, 'prop2' => null}
但因为你的场景中T是接口,所以第一种方式更适合。
内容的提问来源于stack exchange,提问作者KM- MK
相关产品推荐
相关产品推荐

