You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何分离传递泛型函数与参数并解决类型断言及多余参数问题?

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' }) // 期望报错但未触发

存在两个核心问题:

  1. 如何避免使用queryFunction((params as unknown) as T)这类强制类型转换?
  2. 如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 11:55:35