如何在TypeScript的validateField函数中正确收窄类型?
如何在validateField函数中正确收窄TypeScript类型?
先看问题代码及报错场景:
type TStringValidator = (v: string) => void; type TNumberValidator = (v: number) => void; type TFields = 'inn' | 'amount'; interface IValidators { inn: TStringValidator[]; amount: TNumberValidator[]; } const handleString: TStringValidator = (value: string) => {}; const handleNumber: TNumberValidator = (value: number) => {}; const validators = { inn: [handleString], amount: [handleNumber], } satisfies IValidators; function validateField(field: TFields, value: string | number) { const fieldValidators = validators[field]; for (const validator of fieldValidators) { // 触发TS错误:Argument of type 'string | number' is not assignable to parameter of type 'never' const result = validator(value); } return; }
问题原因
TypeScript无法自动建立field和value的类型绑定:field是'inn' | 'amount'联合类型,value是string | number联合类型,两者的对应关系未被编译器识别。此时validators[field]的类型为TStringValidator[] | TNumberValidator[],调用validator时,参数需要同时满足两种校验器的参数类型(即string & number,等价于never),因此报错。
解决方案1:泛型绑定类型关联
通过泛型约束,让field和value的类型一一对应,编译器就能正确推断校验器的参数类型:
type TStringValidator = (v: string) => void; type TNumberValidator = (v: number) => void; type TFields = 'inn' | 'amount'; interface IValidators { inn: TStringValidator[]; amount: TNumberValidator[]; } const handleString: TStringValidator = (value: string) => {}; const handleNumber: TNumberValidator = (value: number) => {}; const validators = { inn: [handleString], amount: [handleNumber], } satisfies IValidators; // 用泛型F约束field,关联对应字段的value类型 function validateField<F extends TFields>( field: F, value: Parameters<IValidators[F][number]>[0] ) { const fieldValidators = validators[field]; for (const validator of fieldValidators) { validator(value); // 类型匹配,无报错 } return; } // 类型检查生效 validateField('inn', '123456'); // 合法 validateField('amount', 100); // 合法 validateField('inn', 123); // 报错:'inn'对应的value必须是string validateField('amount', '100'); // 报错:'amount'对应的value必须是number
解决方案2:函数重载
如果偏好显式定义每种字段的参数类型,可以用函数重载实现:
type TStringValidator = (v: string) => void; type TNumberValidator = (v: number) => void; type TFields = 'inn' | 'amount'; interface IValidators { inn: TStringValidator[]; amount: TNumberValidator[]; } const handleString: TStringValidator = (value: string) => {}; const handleNumber: TNumberValidator = (value: number) => {}; const validators = { inn: [handleString], amount: [handleNumber], } satisfies IValidators; // 定义重载签名,明确字段与参数类型的对应关系 function validateField(field: 'inn', value: string): void; function validateField(field: 'amount', value: number): void; // 实现签名 function validateField(field: TFields, value: string | number) { const fieldValidators = validators[field]; for (const validator of fieldValidators) { // 通过field判断自动收窄类型 if (field === 'inn') { validator(value); } else { validator(value); } } return; }
这种方式下,编译器会根据重载签名做参数类型校验,实现内部也能通过field的判断自动收窄类型,无需额外断言。
内容的提问来源于stack exchange,提问作者Pavel
相关产品推荐
相关产品推荐

