如何为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); });
关键说明
为什么之前的尝试无效?
- 你之前错误地在链式调用时重新指定泛型(比如
prefix1<{ itemId: string }>),但实际上所有收集的函数都应该接收同一个类型的对象,泛型只需要在第一次调用prefixer时指定一次。 - 没有使用递归交叉类型来描述函数的双重调用行为,导致TypeScript无法识别链式调用和最终调用的类型。
- 你之前错误地在链式调用时重新指定泛型(比如
类型安全的体现
- 如果传入的函数参数不符合
T类型(比如访问params.nonExistentProp),TypeScript会立即报错。 - 如果传入的对象不符合
T类型,也会收到类型错误提示。 - 所有链式调用的函数都会自动获得
T类型的参数提示,提升开发体验。
- 如果传入的函数参数不符合
内容的提问来源于stack exchange,提问作者Louay Al-osh
相关产品推荐
相关产品推荐

