如何分离传递泛型函数与参数并解决类型断言及多余参数问题?
TypeScript泛型回调函数类型优化方案
问题描述
需求是将泛型函数作为回调传递给另一个函数,随后带参数调用该泛型函数。当前实现代码如下:
export const prepareFunction = < T extends undefined | Record<string, unknown>, U extends () => void >( queryFunction: (args: T) => { queryFn: U }, ) => { const call = ({ data, ...params }: { data: Awaited<ReturnType<U>> } & T) => { queryFunction((params as unknown) as T) } return call } const functionWithParams = ({ param }: { param: string }) => { return { queryFn: () => 404 } } const functionWithoutParams = () => { return { queryFn: () => 404 } } const callWithParams = prepareFunction(functionWithParams) callWithParams({ data: 404, param: 'param' }) callWithParams({ data: 404 }) // 正确报错 const callWithOutParams = prepareFunction(functionWithoutParams) callWithOutParams({ data: 404 }) callWithOutParams({ data: 404, param: 'should fail' }) // 期望报错但未触发
存在两个核心问题:
- 如何避免使用
queryFunction((params as unknown) as T)这类强制类型转换? - 如何让TypeScript在传递不存在的属性时触发报错?
解决方案
1. 移除类型断言,实现类型安全的参数传递
问题根源在于原泛型约束未清晰区分「无参数」和「有参数」的回调场景,导致解构后的params类型无法被TypeScript正确推断。优化思路:
- 放宽泛型
T的约束,直接对应回调函数的参数类型(支持void表示无参数) - 通过条件类型定义
call函数的参数类型,明确区分无参数/有参数场景 - 根据
params的结构判断回调调用方式,避免强制类型转换
2. 触发额外属性报错,实现严格参数校验
TypeScript对直接传递的对象字面量默认会做额外属性检查,只需确保call函数的参数类型是精确匹配的:
- 无参数回调的
call参数只能包含data字段 - 有参数回调的
call参数只能包含data和回调要求的字段,多余字段直接触发报错
完整优化代码
export const prepareFunction = <T, U extends () => unknown>( queryFunction: (args: T) => { queryFn: U }, ) => { // 定义call函数的参数类型:无参数回调只接受data,有参数回调接受data+回调所需参数 type CallParams = T extends void ? { data: Awaited<ReturnType<U>> } : { data: Awaited<ReturnType<U>> } & T; const call = ({ data, ...params }: CallParams) => { // 根据params是否为空,判断回调函数是否需要参数 if (Object.keys(params).length === 0) { // 无参数场景:直接调用回调 (queryFunction as () => void)(); } else { // 有参数场景:params类型与T完全匹配,直接传递 queryFunction(params as T); } }; return call; }; // 测试用例 const functionWithParams = ({ param }: { param: string }) => { return { queryFn: () => 404 } }; const functionWithoutParams = () => { return { queryFn: () => 404 } }; const callWithParams = prepareFunction(functionWithParams); callWithParams({ data: 404, param: 'param' }); // 正常通过 callWithParams({ data: 404 }); // 正确报错:缺少param const callWithOutParams = prepareFunction(functionWithoutParams); callWithOutParams({ data: 404 }); // 正常通过 callWithOutParams({ data: 404, param: 'should fail' }); // 正确报错:param不存在于类型中
额外说明
如果需要对非字面量对象也进行严格的额外属性校验,可以添加精确类型约束:
// 定义精确类型工具,禁止额外属性 type Exact<T, U> = T & Record<Exclude<keyof U, keyof T>, never>; // 修改CallParams定义 type CallParams = T extends void ? Exact<{ data: Awaited<ReturnType<U>> }, { data: Awaited<ReturnType<U>> }> : Exact<{ data: Awaited<ReturnType<U>> } & T, { data: Awaited<ReturnType<U>> } & T>;
这样即使是变量传递的对象,只要包含多余属性也会触发报错。
内容的提问来源于stack exchange,提问作者Arnas Savickas
相关产品推荐
相关产品推荐

