求助:为类Zod的TypeScript库实现自定义类型推断
实现类Zod库的TypeScript类型推断
核心思路是通过泛型绑定运行时Schema实例与编译时类型,配合TypeScript的条件类型、索引类型实现自动类型推断。以下是分步实现方案:
1. 定义基础Schema基类
先创建一个带泛型的基类,泛型T表示当前Schema对应的编译时类型,所有具体类型的Schema都继承这个基类:
class Schema<T> { // 你的运行时校验逻辑(已实现部分) parse(input: unknown): T { // 这里替换为你已有的校验逻辑,返回值会自动带上T类型 return input as T; } }
2. 实现基础类型Schema
为string、number等基础类型创建子类,绑定对应的泛型类型:
class StringSchema extends Schema<string> { parse(input: unknown): string { if (typeof input !== 'string') throw new Error('Invalid string'); return input; } } class NumberSchema extends Schema<number> { parse(input: unknown): number { if (typeof input !== 'number') throw new Error('Invalid number'); return input; } } // 提供Zod风格的便捷创建函数 export const string = () => new StringSchema(); export const number = () => new NumberSchema();
此时使用const strSchema = string();,TypeScript会自动推断strSchema的类型为Schema<string>,调用strSchema.parse("test")返回值类型为string。
3. 实现Object类型的自动类型推断(核心)
这是你困惑的关键部分,需要通过工具类型从运行时的对象Schema结构中提取编译时类型:
第一步:定义类型推断工具
用索引类型和条件类型的infer关键字,将Schema组成的对象转换为对应的TS对象类型:
// 工具类型:从Schema对象中提取编译时类型 type InferSchema<T> = { [K in keyof T]: T[K] extends Schema<infer U> ? U : never; };
这个类型会遍历输入对象的每个属性,取出每个Schema实例对应的泛型类型U,最终组合成完整的对象类型。
第二步:实现ObjectSchema类与创建函数
class ObjectSchema<T> extends Schema<T> { private readonly schema: Record<string, Schema<any>>; constructor(schema: Record<string, Schema<any>>) { super(); this.schema = schema; } parse(input: unknown): T { if (typeof input !== 'object' || input === null) { throw new Error('Input is not an object'); } const result = {} as T; for (const [key, fieldSchema] of Object.entries(this.schema)) { result[key as keyof T] = fieldSchema.parse((input as any)[key]); } return result; } } // 自动推断泛型的便捷函数 export function object<S extends Record<string, Schema<any>>>( schema: S ): ObjectSchema<InferSchema<S>> { return new ObjectSchema(schema); }
测试效果
const userSchema = object({ name: string(), age: number(), email: string() }); // TypeScript会自动推断userSchema的类型为ObjectSchema<{ // name: string; // age: number; // email: string; // }> const user = userSchema.parse({ name: "Alice", age: 30, email: "alice@example.com" }); // user的类型自动推断为上述对象类型,无需手动定义
4. 扩展到其他类型(比如数组)
用同样的思路扩展数组类型:
class ArraySchema<T> extends Schema<T[]> { private readonly itemSchema: Schema<T>; constructor(itemSchema: Schema<T>) { super(); this.itemSchema = itemSchema; } parse(input: unknown): T[] { if (!Array.isArray(input)) throw new Error('Input is not an array'); return input.map(item => this.itemSchema.parse(item)); } } export function array<T>(itemSchema: Schema<T>): ArraySchema<T> { return new ArraySchema(itemSchema); } // 使用示例 const stringListSchema = array(string()); const list = stringListSchema.parse(["a", "b", "c"]); // list的类型自动推断为string[]
关键原理总结
- 每个Schema实例通过泛型
T与编译时类型绑定,这是运行时与编译时的连接点。 InferSchema这类工具类型利用TypeScript的infer关键字,从Schema实例中提取出对应的泛型类型。- 便捷创建函数(如
object)依靠TypeScript的自动泛型推断能力,从输入的Schema结构自动推导最终的编译时类型,无需手动指定。
内容的提问来源于stack exchange,提问作者Michael Sohnen
相关产品推荐
相关产品推荐

