如何在TypeScript中基于记录常量的复杂类型约束函数参数
解决方案
要实现这个需求,核心是保留AllCollections的精确类型信息(不要手动指定宽泛的Record<string, AnyCollection>),然后通过条件类型动态约束函数参数和返回值。
步骤1:修正集合常量的类型推导
首先去掉AllCollections的手动类型注解,并用as const锁定字面量类型,让TypeScript能精确推导每个集合的具体类型:
import { z } from "zod"; export type HomogeneousCollection = { schema: z.ZodType<any>; type: "homogeneous"; }; export type HeterogeneousCollection = { schemas: Record<string, z.ZodType<any>>; type: "heterogeneous"; }; export type AnyCollection = HomogeneousCollection | HeterogeneousCollection; // 自动推导精确类型,用as const锁定字面量 export const AllCollections = { collectionA: { type: "homogeneous" as const, schema: z.object({ foo: z.number() }) }, collectionB: { type: "heterogeneous" as const, schemas: { docA: z.object({ bar: z.number() }), docB: z.object({ baz: z.number() }) } } } as const;
步骤2:定义泛型辅助类型
提取集合名称类型、集合类型,并通过条件类型定义函数的参数规则:
// 提取所有集合名称的字面量类型 type CollectionNames = keyof typeof AllCollections; // 根据集合名称获取对应的集合类型 type GetCollection<C extends CollectionNames> = typeof AllCollections[C]; // 定义函数的参数类型:根据集合类型动态生成 type FunctionArgs<C extends CollectionNames> = GetCollection<C> extends HomogeneousCollection ? [collectionName: C] : [collectionName: C, options: { documentName: keyof GetCollection<C>['schemas'] }]; // 定义返回值类型:根据参数提取对应的Zod schema的infer类型 type ReturnSchema<C extends CollectionNames, Args extends any[]> = Args extends [C] ? z.infer<GetCollection<C>['schema']> : Args extends [C, { documentName: infer D }] ? D extends keyof GetCollection<C>['schemas'] ? z.infer<GetCollection<C>['schemas'][D]> : never : never;
步骤3:实现约束函数
用泛型和Rest参数结合条件类型,实现符合要求的函数:
function f<C extends CollectionNames, Args extends FunctionArgs<C>>( ...args: Args ): ReturnSchema<C, Args> { const [collectionName, options] = args; const collection = AllCollections[collectionName]; if (collection.type === "homogeneous") { return collection.schema.parse({}) as ReturnSchema<C, Args>; } else { if (!options) throw new Error("需传入documentName"); const schema = collection.schemas[options.documentName]; return schema.parse({}) as ReturnSchema<C, Args>; } }
验证效果
现在函数会严格约束参数,并正确推导返回值:
// ✅ 编译通过,返回类型为 { foo: number } f("collectionA"); // ✅ 编译通过,返回类型为 { baz: number } f("collectionB", { documentName: "docB" }); // ❌ 编译失败:缺少第二个参数 f("collectionB"); // ❌ 编译失败:documentName不是有效取值 f("collectionB", { documentName: "invalidDoc" });
关键说明
- 用
as const锁定AllCollections的字面量类型是核心,这样TypeScript才能区分每个集合是同质还是异质,以及具体的文档名称。 - 条件类型
FunctionArgs动态生成参数列表,确保同质集合只传名称,异质集合必须传名称+文档名。 - 返回类型
ReturnSchema根据参数自动推导对应的Zod类型,无需手动指定泛型参数。
内容的提问来源于stack exchange,提问作者Nathan
相关产品推荐
相关产品推荐

