如何用条件类型替代函数重载实现callChild的类型约束?
用条件类型替代函数重载优化callChild函数类型提示
我写了一个callChild函数,逻辑是根据传入的选项调用回调:如果提供了args选项,回调就使用该选项指定的参数;未提供则使用默认参数ArgsDefault。目前通过函数重载实现了该逻辑,代码如下:
type ArgsDefault = { argDefault: boolean }; const argsDefault: ArgsDefault = { argDefault: true }; function callChild<Args extends Record<string, unknown>>( // 若提供options.args,回调使用其返回类型 options: { args: () => Args; }, child: (args: Args) => void, ): void; function callChild( // 若未提供options.args,回调使用ArgsDefault options: Record<string, never>, child: (args: ArgsDefault) => void, ): void; function callChild<Args extends Record<string, unknown>>( options: Partial<{ args: () => Args }>, child: (args: Args | ArgsDefault) => void ) { const args = typeof options.args === `function` ? options.args() : argsDefault; child(args); } // 调用测试 callChild( { args: () => ({ argCustom: true }) }, (args) => console.log(args.argCustom), // 此处不会提示argDefault ); callChild( {}, (args) => console.log(args.argDefault), // 此处不会提示argCustom );
函数重载能实现正确的类型提示,但存在大量重复代码。我尝试用条件类型替代重载,但遇到了类型错误,代码如下:
type ArgsDefault = { argDefault: boolean }; const argsDefault: ArgsDefault = { argDefault: true }; function callChild< Args extends Record<string, unknown>, Options extends Partial<{ args: () => Args; }> >( options: Options, child: (args: `args` extends keyof Options ? Args : ArgsDefault) => void ) { const args = typeof options.args === `function` ? options.args() : argsDefault; child(args); // 错误:Type 'Record<string, unknown>' is not assignable to type '"args" extends keyof Options ? Args : { argDefault: boolean; }' }
解决方案
问题出在泛型推断的关联性上:原代码中Options和Args的约束无法让TypeScript精准关联条件类型与运行时逻辑。可以通过直接基于Options泛型做条件类型推断来解决,同时添加安全的类型断言让函数体通过类型检查:
type ArgsDefault = { argDefault: boolean }; const argsDefault: ArgsDefault = { argDefault: true }; function callChild<Options extends Partial<{ args: () => Record<string, unknown> }>>( options: Options, child: (args: Options extends { args: () => infer A } ? A : ArgsDefault) => void ) { const args = typeof options.args === 'function' ? options.args() : argsDefault; // 类型断言:运行时逻辑与条件类型完全匹配,断言是安全的 child(args as Options extends { args: () => infer A } ? A : ArgsDefault); } // 测试调用,类型提示正常 callChild( { args: () => ({ argCustom: true }) }, (args) => console.log(args.argCustom) // 正确提示argCustom ); callChild( {}, (args) => console.log(args.argDefault) // 正确提示argDefault );
说明
- 用
Options extends { args: () => infer A }直接判断options是否包含args属性,并提取其返回类型A,替代了原有的重载分支 - 函数体中添加类型断言是因为TypeScript无法自动将
args的联合类型与条件类型关联,但我们的运行时逻辑和条件类型完全一致,断言是安全的 - 这种写法消除了重载的重复代码,同时保留了和原重载完全一致的类型提示效果
内容的提问来源于stack exchange,提问作者RobertAKARobin
相关产品推荐
相关产品推荐

