如何在TypeScript的映射类型中引用静态字段?
嘿,这个问题我太熟了!其实你之前的代码能拿到实例成员的键,是因为ClassWithFields在TypeScript里直接当类型用的时候,指的是类的实例类型——也就是new出来的对象的类型,所以keyof ClassWithFields自然只能拿到实例成员的名字。
但静态成员不一样,它们是挂在类的构造函数上的,对应的类型是typeof ClassWithFields(注意多了个typeof),而不是ClassWithFields本身。所以要拿静态成员的键,得先拿到构造函数的类型才行。
我给你两种方案,看你习惯哪种:
方案1:和你原来的调用习惯完全兼容
如果你想保持和之前一样的调用方式(直接传ClassWithFields而不是typeof ClassWithFields),可以先写一个辅助类型,从实例类型反向拿到构造函数类型,再从中提取静态成员:
// 辅助类型:从实例类型获取对应的构造函数类型 type ConstructorOf<TInstance> = typeof { new (...args: any[]): TInstance }; // 获取静态字段的类型 type GetStaticFieldType<TInstance, TStaticKey extends keyof ConstructorOf<TInstance>> = ConstructorOf<TInstance>[TStaticKey]; // 获取静态字段的键列表(就是你要的"c"|"d"这种) type GetStaticFieldKeys<TInstance, TStaticKey extends keyof ConstructorOf<TInstance>> = keyof GetStaticFieldType<TInstance, TStaticKey>;
然后直接用你想要的方式调用就行,完全不用改传入的第一个参数:
// 测试一下,这个会直接解析为"c" | "d",IDE还会自动提示"staticMember"这个选项 const testStaticKeys: GetStaticFieldKeys<ClassWithFields, "staticMember">;
方案2:更直接的极简写法
如果你觉得上面的辅助类型有点绕,其实可以直接传入构造函数类型(typeof ClassWithFields),写法更简洁:
// 获取静态字段的类型 type GetStaticFieldType<TConstructor, TStaticKey extends keyof TConstructor> = TConstructor[TStaticKey]; // 获取静态字段的键列表 type GetStaticFieldKeys<TConstructor, TStaticKey extends keyof TConstructor> = keyof GetStaticFieldType<TConstructor, TStaticKey>;
调用的时候只需要把第一个参数改成typeof ClassWithFields:
const testStaticKeys: GetStaticFieldKeys<typeof ClassWithFields, "staticMember">; // 同样会解析为"c" | "d",智能提示也正常
为啥原来的写法拿不到静态字段?
再给你掰扯清楚底层逻辑:
- 当你写
ClassWithFields作为类型时,TypeScript默认把它当成实例类型——也就是new这个类出来的对象的类型,所以keyof ClassWithFields只能拿到实例成员的名字。 - 静态成员是挂在类本身(构造函数)上的,对应的类型是
typeof ClassWithFields,所以keyof typeof ClassWithFields才能拿到静态成员的名字。
你原来的getClassFieldFields能拿到实例成员的子键,本质是先取了实例类型的属性,再取属性的键;现在要拿静态成员的子键,就是先取构造函数类型的静态属性,再取这个属性的键,逻辑是完全一致的,只是类型的来源变了。
这样写应该就完全满足你的需求了,IDE的智能提示也会正常工作,和你原来用实例类型的体验一模一样~
备注:内容来源于stack exchange,提问作者Tomáš Zato

