如何从模型配置对象动态生成TypeScript类型定义?
如何从Model配置自动推导TypeScript类型
问题背景
我有一套原生JavaScript模型系统,通过重写publicAttributes方法定义字段的类型转换规则,实例化时会自动将输入数据转换为指定类型。现在迁移到TypeScript后,希望避免重复编写类属性的类型定义,直接从publicAttributes配置自动推导类型,在赋值阶段获得TypeScript的类型检查支持,消除代码冗余。
之前考虑过开发TS插件或代码生成工具,但没找到现成方案,想问这个需求是否可行?如果可行,最佳实现方式是什么?
可行性结论
完全可行。借助TypeScript的高级类型特性(条件类型、映射类型、类型推断),不需要额外工具,纯类型层面就能实现从publicAttributes到TS类型的自动推导。
最佳实现方案
核心思路是通过TypeScript泛型和条件类型,将publicAttributes中定义的类型转换规则映射为对应的TS类型,同时改造Model基类的类型定义,让子类自动继承推导后的实例类型。
1. 定义类型转换辅助工具
先编写几个核心类型工具,用来解析publicAttributes中的各种类型规则:
// 将构造函数转换为对应的实例/值类型 type ConstructorToType<T> = T extends typeof String ? string : T extends typeof Number ? number : T extends typeof Date ? Date : T extends new (...args: any[]) => infer Instance ? Instance : T; // 解析publicAttributes中单个字段的最终TS类型 type CastType<T> = // 处理数组类型(如[String]) T extends Array<infer U> ? Array<ConstructorToType<U>> : // 处理动态类型函数(返回构造函数或null) T extends (value: any) => infer R ? ConstructorToType<Exclude<R, null>> | null : // 处理普通构造函数或类型 ConstructorToType<T>; // 从publicAttributes映射出完整的实例类型 type ModelInstanceType<Attributes> = { [K in keyof Attributes]?: CastType<Attributes[K]>; };
2. 改造Model基类
修改Model的类型定义,引入泛型参数接收publicAttributes的类型,并自动推导实例的类型:
class Model<Attributes extends Record<string, any>> { // 声明实例的动态属性类型 declare [key: string]: any; // 约束publicAttributes的类型 declare publicAttributes: Attributes; cast(value: any, type: any = null): any { if (type === Date) { return new Date(value); } else if (type === Number) { return Number(value); } else if (type === String) { return value.toString(); } else if (Model.isModel(type)) { const model = new type(value); return model; } else if (Array.isArray(type)) { return value.map((item: any) => this.cast(item, type[0])); } else if (typeof type === 'function') { return this.cast(value, type(value)); } return value; } static isModel(val: any): boolean { return ( val?.prototype instanceof Model || val?.constructor?.prototype instanceof Model ); } // 构造函数参数类型自动匹配推导后的实例类型 constructor(data: Partial<ModelInstanceType<Attributes>> = {}) { for (const [key, value] of Object.entries(data)) { this[key] = this.cast(value, this.publicAttributes[key] || null); } } }
3. 子类实现(无需手动定义属性类型)
子类只需实现publicAttributes方法,TypeScript会自动推导实例的完整类型:
class Pet extends Model<ReturnType<Pet['publicAttributes']>> { get publicAttributes() { return { name: String, type: String, }; } } class Person extends Model<ReturnType<Person['publicAttributes']>> { get publicAttributes() { return { joined: Date, name: String, age: Number, skills: [String], locale: (v: any) => v === 'vacation' ? null : String, pets: [Pet], }; } }
4. 验证类型检查效果
实例化时会自动进行类型校验,且实例的属性类型完全匹配推导结果:
const bill = new Person({ joined: '2021-02-10', // 类型检查通过,运行时转为Date name: 'bill', age: '26', // 类型检查通过,运行时转为number skills: ['python', 'dogwalking', 500], // 运行时转为string数组 locale: 'vacation', // 运行时转为null pets: [ { name: 'rufus', type: 'dog', }, ], }); // 访问属性时获得正确的类型提示 bill.joined; // Date | undefined bill.age; // number | undefined bill.pets; // Pet[] | undefined bill.locale; // string | null | undefined
方案优势
- 零代码冗余:仅需维护
publicAttributes,无需重复编写类属性类型 - 完整类型安全:实例化和属性访问时自动获得TS类型检查
- 兼容原有逻辑:完全保留原JS模型系统的运行时转换逻辑
- 无额外依赖:纯TypeScript类型系统实现,无需插件或代码生成工具
内容的提问来源于stack exchange,提问作者c.m
相关产品推荐
相关产品推荐

