TypeScript强类型函数实现:将类映射为小写首字母键的实例对象
实现带强类型约束的TypeScript createObject函数,解决类名提取never问题
问题背景
需要实现一个createObject函数,接收多个类作为参数,返回的对象键为类名首字母小写形式,值为对应类的实例,且需具备完整的类型提示。当前函数无类型约束,同时尝试提取类名时得到never类型。
现有代码
function createObject (...classes: any[]) { return classes.reduce((acc, clazz) => { const name = clazz.name.charAt(0).toLowerCase() + clazz.name.slice(1); acc[name] = new clazz(); return acc; }, {}); }
期望用法
const { aClass, bClass, cClass } = createObject(AClass, BClass, CClass);
类名提取失败代码
type GetName<T> = 'name' extends keyof T ? T['name'] : never; type ClassName = GetName<AClass>; // 类型为never
问题原因
TypeScript默认会把类的name属性类型推断为string,而非具体的类名字面量(比如"AClass"),因此GetName<AClass>无法匹配到预期的字面量类型,最终返回never。
解决方案
1. 构建类名与实例的类型映射工具
先实现类型层面的类名提取、首字母小写转换,以及类到实例的映射:
// 定义可实例化的类构造函数类型,保留name属性的字面量类型 type ClassConstructor<T = any> = new (...args: any[]) => T & { constructor: { name: string } }; // 类型工具:将字符串首字母转为小写 type LowerFirst<T extends string> = T extends `${infer First}${infer Rest}` ? `${Lowercase<First>}${Rest}` : T; // 类型工具:将类数组转换为「小写类名: 实例类型」的映射 type ClassInstanceMap<T extends ClassConstructor[]> = { [K in keyof T as T[K] extends ClassConstructor ? LowerFirst<T[K]['constructor']['name']> : never]: T[K] extends ClassConstructor<infer Instance> ? Instance : never; };
2. 实现带强类型的createObject函数
结合上述类型工具,改造函数实现类型约束:
function createObject<T extends ClassConstructor[]>(...classes: [...T]): ClassInstanceMap<T> { return classes.reduce((acc, clazz) => { const name = clazz.name.charAt(0).toLowerCase() + clazz.name.slice(1) as LowerFirst<typeof clazz['name']>; acc[name] = new clazz(); return acc; }, {} as ClassInstanceMap<T>); }
3. 测试验证
定义测试类并使用函数:
class AClass { aProp = 'a'; } class BClass { bProp = 'b'; } // 使用函数,自动获得类型提示 const { aClass, bClass } = createObject(AClass, BClass); console.log(aClass.aProp); // 类型提示正常,不会报错
关键说明
[...T]元组参数类型确保TypeScript能正确推断每个传入类的具体类型,而非合并为模糊的联合类型。ClassInstanceMap通过映射类型,将每个类的类型转换为对应键值对,完全匹配运行时的键值逻辑。- 类型工具与运行时逻辑一一对应,保证类型提示和实际行为一致。
内容的提问来源于stack exchange,提问作者Kindzoku
相关产品推荐
相关产品推荐

