如何让createParser的配置对象自动匹配parse的参数与返回值类型
TypeScript 类型约束:确保Parser的defaultValue与parse入参、defaultValueOut与parse返回值匹配
我之前在Stack Overflow提过类似问题(感谢用户jcalz),但现在条件变更后问题更复杂。以下是我最简洁的尝试代码:
type ParserFunction<In, Out> = (value: In) => Out; interface Parser<In, Out> { parse: ParserFunction<In, Out>; defaultValue?: In; defaultValueOut?: Out; } function createParser< TParser extends Parser<any, any>, TFields extends Record<string, TParser> >(options: { [K in keyof TFields]: TFields[K] extends Parser<infer I, infer O> ? Parser<I, O> : never }) { return void 0; } createParser({ pass: { defaultValue: '100', defaultValueOut: 100, parse(value: string) { return Number(value) } }, fail: { defaultValue: '100', // 此处应为number类型 defaultValueOut: 100, // 此处应为string类型 parse(value: number) { return String(value) } } })
需要实现的效果是:调用createParser时,每个Parser对象的defaultValue类型必须与parse的入参类型一致,defaultValueOut类型必须与parse的返回值类型一致。
解决方案
问题出在原代码的泛型约束上:TParser extends Parser<any, any>会让所有字段共享同一种In和Out类型,导致TypeScript无法为每个字段独立推断类型。我们需要修改泛型定义,让每个字段可以拥有自己的In和Out类型。
方案一:简化泛型约束
直接让TFields是Record<string, Parser<any, any>>,利用TypeScript的类型推断自动为每个字段匹配正确的In和Out:
type ParserFunction<In, Out> = (value: In) => Out; interface Parser<In, Out> { parse: ParserFunction<In, Out>; defaultValue?: In; defaultValueOut?: Out; } function createParser<TFields extends Record<string, Parser<any, any>>>( options: { [K in keyof TFields]: TFields[K] extends Parser<infer I, infer O> ? Parser<I, O> : never } ) { return void 0; } createParser({ pass: { defaultValue: '100', defaultValueOut: 100, parse(value: string) { return Number(value) } }, fail: { defaultValue: 100, // 类型正确,传入string会触发错误 defaultValueOut: '100', // 类型正确,传入number会触发错误 parse(value: number) { return String(value) } } })
方案二:使用辅助类型明确约束
如果需要更清晰的类型关系,可以定义一个辅助类型ParserRecord,直接从每个字段的parse函数推断In和Out,再约束整个对象的类型:
type ParserFunction<In, Out> = (value: In) => Out; interface Parser<In, Out> { parse: ParserFunction<In, Out>; defaultValue?: In; defaultValueOut?: Out; } type ParserRecord<T extends Record<string, { parse: (...args: any) => any }>> = { [K in keyof T]: T[K]['parse'] extends (value: infer I) => infer O ? Parser<I, O> : never }; function createParser<T extends Record<string, { parse: (...args: any) => any }>>( options: ParserRecord<T> ) { return void 0; } createParser({ pass: { defaultValue: '100', defaultValueOut: 100, parse(value: string) { return Number(value) } }, fail: { defaultValue: 100, defaultValueOut: '100', parse(value: number) { return String(value) } } })
两种方案都能实现需求:当defaultValue或defaultValueOut的类型与parse的入参/返回值不匹配时,TypeScript会立即抛出类型错误。
内容的提问来源于stack exchange,提问作者Jordan
相关产品推荐
相关产品推荐

