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

如何为TypeScript中的递归式prefixer函数定义正确的泛型类型

解决TypeScript递归链式函数的类型定义问题

要实现类型安全的prefixer函数,核心是利用递归交叉类型来描述它既能链式接收函数、又能最终接收对象返回结果的行为。下面是完整的解决方案:

步骤1:定义递归的Prefixer类型

首先我们需要一个类型,它同时支持两种调用方式:

  • 接收一个处理T类型对象的函数,返回另一个Prefixer<T>(链式调用)
  • 接收一个T类型对象,返回拼接后的字符串(最终调用)
type Prefixer<T> = 
  ((fn: (params: T) => string) => Prefixer<T>) & 
  ((params: T) => string);

这个交叉类型完美匹配了我们需要的递归行为:每次传入函数都会返回相同类型的Prefixer,直到传入对象时输出结果。

步骤2:实现类型安全的prefixer函数

接下来调整原函数的实现,给所有部分加上正确的类型约束,同时修正原代码中identity函数的bug(原函数返回对象会导致非预期的[object Object]字符串):

export function prefixer<T>(func1?: (params: T) => string): Prefixer<T> {
  // 修正identity:返回空字符串,确保初始拼接的正确性
  const identity = (_: T) => '';
  const initialFns = func1 ? [func1] : [identity];

  function curriedPrefixer(fns: Array<(params: T) => string>): Prefixer<T> {
    // 使用类型断言告诉TS这个函数符合Prefixer<T>的交叉类型
    return ((param: T | ((params: T) => string)) => {
      if (typeof param === 'function') {
        return curriedPrefixer(fns.concat(param as (params: T) => string));
      }
      // 额外判断null,避免typeof null === 'object'的坑
      if (typeof param === 'object' && param !== null) {
        return fns.reduce((prev, fn) => prev + fn(param), '');
      }
      return ''; // 非法输入返回空字符串,也可根据需求返回undefined
    }) as Prefixer<T>;
  }

  return curriedPrefixer(initialFns);
}

步骤3:类型安全的测试用例

现在你可以去掉所有any,享受完整的类型提示和校验:

test('should auto prefix', () => {
  // 定义最终传入的参数对象类型
  type AppParams = { domain: string; itemId: string };

  // 第一次调用指定泛型,后续链式调用自动继承类型
  let prefix1 = prefixer<AppParams>((params) => `https://wwww.${params.domain}.com`);
  // 这里的函数会自动获得params的类型提示
  let prefix2 = prefix1(() => '/path/to/item');
  let prefix3 = prefix2((params) => `/${params.itemId}`);
  
  const params: AppParams = { domain: 'google', itemId: '5444' };
  
  let resolvedString1 = prefix1(params);
  let resolvedString2 = prefix2(params);
  let resolvedString3 = prefix3(params);
  
  let trueResult1 = `https://wwww.${params.domain}.com`;
  let trueResult2 = `https://wwww.${params.domain}.com/path/to/item`;
  let trueResult3 = `https://wwww.${params.domain}.com/path/to/item/${params.itemId}`;
  
  expect(resolvedString1).toEqual(trueResult1);
  expect(resolvedString2).toEqual(trueResult2);
  expect(resolvedString3).toEqual(trueResult3);
});

关键说明

  1. 为什么之前的尝试无效?

    • 你之前错误地在链式调用时重新指定泛型(比如prefix1<{ itemId: string }>),但实际上所有收集的函数都应该接收同一个类型的对象,泛型只需要在第一次调用prefixer时指定一次。
    • 没有使用递归交叉类型来描述函数的双重调用行为,导致TypeScript无法识别链式调用和最终调用的类型。
  2. 类型安全的体现

    • 如果传入的函数参数不符合T类型(比如访问params.nonExistentProp),TypeScript会立即报错。
    • 如果传入的对象不符合T类型,也会收到类型错误提示。
    • 所有链式调用的函数都会自动获得T类型的参数提示,提升开发体验。

内容的提问来源于stack exchange,提问作者Louay Al-osh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 21:57:51