TypeScript条件类型定义表单项数组时值类型校验失效问题
问题现象
需要定义表单项填充数据的TypeScript类型,要求value字段类型严格绑定当前项的type字段取值,现有实现单类型校验正常,但数组场景下校验失效:
- 给
FillFormItem传入明确泛型(如AntdInputType.InputText)时,value类型校验正常,不符合映射规则的赋值会抛出类型错误 - 使用
FillFormItem<keyof ValueOfTypeMap>[]定义表单项数组时校验失效:即使数组内项的type明确为AntdInputType.InputText,给value传入string[]类型也不会触发报错,此时value类型被推导为所有控件值类型的联合类型string | Date | string[],丢失了和type字段的绑定关系。
现有实现代码:
enum AntdInputType { InputText = 'InputText', DatePicker = 'DatePicker', TreeSelect = 'TreeSelect', // ...其他控件类型 } type ValueOfTypeMap = { [AntdInputType.InputText]: string, [AntdInputType.DatePicker]: string | Date, [AntdInputType.TreeSelect]: string[], // ...其他控件对应的值类型 } type FillFormItem<Type extends keyof ValueOfTypeMap> = { label: string type: Type value: ValueOfTypeMap[Type] } // 单元素校验正常 const fill: FillFormItem<AntdInputType.InputText> = { label: 'test', type: AntdInputType.InputText, value: 'word', // value: ['word'] // 此处会正常抛错 } // 数组场景校验失效 const fillValues: FillFormItem<keyof ValueOfTypeMap>[] = [ { label: 'text', type: AntdInputType.InputText, value: ['text'], // 错误赋值不会被拦截 } ]
问题原因
FillFormItem<keyof ValueOfTypeMap>不会自动展开为「每个type对应匹配value」的类型联合,而是直接将泛型参数替换为联合类型求值,最终得到的结构是:
{ label: string; type: AntdInputType.InputText | AntdInputType.DatePicker | AntdInputType.TreeSelect; value: string | Date | string[]; }
这种结构下type和value的类型没有绑定关系,只要两个字段的取值都在各自的联合类型范围内,就不会触发类型错误。
解决方案
通过映射类型遍历所有控件类型,生成真正的可辨识联合(Discriminated Union),让TypeScript可以根据type字段自动收窄value的类型:
// 生成所有合法表单项类型的联合,每个类型的type和value严格匹配 type FillFormItemUnion = { [K in keyof ValueOfTypeMap]: FillFormItem<K> }[keyof ValueOfTypeMap] // 此时数组校验会正常生效 const fillValues: FillFormItemUnion[] = [ { label: 'text', type: AntdInputType.InputText, value: ['text'], // 此处会正确抛出类型错误:string[] 不能赋值给类型 string }, { label: 'date', type: AntdInputType.DatePicker, value: new Date(), // 合法赋值,正常通过 }, { label: 'treeSelect', type: AntdInputType.TreeSelect, value: ['node1', 'node2'], // 合法赋值,正常通过 } ]
上述写法的本质是遍历ValueOfTypeMap的所有key,为每个key单独生成对应的FillFormItem<K>类型,再通过索引访问把这些类型拼成联合,最终等价于:
type FillFormItemUnion = | FillFormItem<AntdInputType.InputText> | FillFormItem<AntdInputType.DatePicker> | FillFormItem<AntdInputType.TreeSelect> // ...其他控件类型
如果希望定义数组时不需要手动标注类型,可以加一个泛型辅助函数自动做类型校验:
const defineFormItems = <T extends FillFormItemUnion[]>(items: T) => items // 直接传入数组即可自动校验每一项的类型匹配 const fillValues2 = defineFormItems([ { label: 'text', type: AntdInputType.InputText, value: ['text'], // 同样会抛出类型错误 } ])
内容的提问来源于stack exchange,提问作者hirohe
相关产品推荐
相关产品推荐

