TypeScript嵌套泛型收窄:如何保障表单Schema中FieldArray的类型安全?
类型安全表单Schema的泛型收窄问题
我正在构建类型安全的表单Schema,其中某一表单项需要对表单类型的子集执行keyof检查,但不知道如何传递并收窄子类型的泛型。具体需求是让FieldArray的fields属性具备与Schema类型一致的类型安全性——当前实现中,fields可以随意添加不符合Form定义的键(比如在errors的fields里加foo、bar、baz不会触发类型警告),但我需要它仅允许包含对应数组元素的合法键(比如rules对应ruleName、isActive、ruleId)。
原实现代码
type FieldType = "text-input" | "number" | "dropdown" | "checkbox"; type Field = { label: string; type: FieldType; }; type FieldName<T> = T[keyof T] extends (infer I)[] ? I : never; type FieldArray<T> = { type: "array"; groupLabel: string; fields: Record<keyof FieldName<T> & string, Field>; }; type SchemaField<T> = Field | FieldArray<T>; type Schema<T> = Record<keyof T, SchemaField<T>>; type Form = { workflowName: string; id: number; rules: { ruleName: string; isActive: boolean; ruleId: number }[]; errors: { errorName: string; isActive: boolean; errorId: number }[]; }; const formSchema: Schema<Form> = { workflowName: { type: "text-input", label: "Name" }, id: { type: "number", label: "Id" }, rules: { type: "array", groupLabel: "Rules", fields: { ruleName: { label: "Rule Name", type: "text-input" }, isActive: { label: "Is Active", type: "checkbox" }, ruleId: { label: "Rule Id", type: "number" }, }, }, errors: { type: "array", groupLabel: "Errors", fields: { errorName: { label: "Error Name", type: "text-input" }, isActive: { label: "Is Active", type: "checkbox" }, errorId: { label: "Error Id", type: "number" }, }, }, };
当前未触发警告的错误示例
errors: { type: "array", groupLabel: "Errors", fields: { foo: { label: "Error Name", type: "text-input" }, bar: { label: "Is Active", type: "checkbox" }, baz: { label: "Error Id", type: "number" }, }, },
解决方案
问题核心是原FieldArray的泛型绑定了整个表单类型,导致fields的键范围被扩大为所有数组元素键的联合。需要调整泛型设计,让每个FieldArray绑定对应数组的元素类型:
type FieldType = "text-input" | "number" | "dropdown" | "checkbox"; type Field = { label: string; type: FieldType; }; // FieldArray泛型直接接收数组元素类型,严格限制fields的键为元素的key type FieldArray<Item> = { type: "array"; groupLabel: string; fields: Record<keyof Item & string, Field>; }; // 根据表单字段类型自动匹配SchemaField:数组类型用FieldArray,其他用Field type SchemaField<Value> = Value extends (infer Item)[] ? FieldArray<Item> : Field; // 遍历表单字段,为每个字段生成对应类型的SchemaField type Schema<Form> = { [K in keyof Form]: SchemaField<Form[K]>; }; type Form = { workflowName: string; id: number; rules: { ruleName: string; isActive: boolean; ruleId: number }[]; errors: { errorName: string; isActive: boolean; errorId: number }[]; }; const formSchema: Schema<Form> = { workflowName: { type: "text-input", label: "Name" }, id: { type: "number", label: "Id" }, rules: { type: "array", groupLabel: "Rules", fields: { ruleName: { label: "Rule Name", type: "text-input" }, isActive: { label: "Is Active", type: "checkbox" }, ruleId: { label: "Rule Id", type: "number" }, }, }, errors: { type: "array", groupLabel: "Errors", fields: { errorName: { label: "Error Name", type: "text-input" }, isActive: { label: "Is Active", type: "checkbox" }, errorId: { label: "Error Id", type: "number" }, // foo: { label: "Test", type: "text-input" } // 此处会触发类型错误,符合预期 }, }, };
关键调整说明
FieldArray泛型优化:将泛型参数改为数组元素类型,确保fields的键严格匹配当前数组元素的属性名。- 条件类型自动匹配:
SchemaField通过条件判断表单字段是否为数组,自动返回对应的数组型或普通型字段定义。 - 映射类型遍历表单:
Schema使用映射类型遍历表单的每个字段,为每个字段生成精准匹配的Schema定义,避免泛型范围过大导致的类型宽松。
修改后,添加非法键会立即触发TypeScript类型警告,完全符合类型安全的需求。
内容的提问来源于stack exchange,提问作者Scott Z
相关产品推荐
相关产品推荐

