如何在TypeScript中强制泛型函数仅接受恰好一个参数(复杂类型定义场景)
如何在TypeScript中强制泛型函数仅接受恰好一个参数(复杂类型定义场景)
嗨,我完全懂你现在的困扰——你想给自定义npm包写一个装饰器生成函数,要求传入的被装饰函数必须恰好接受一个参数,而且要在开启noImplicitAny的JS/TS项目里正常工作。之前的类型定义已经能约束参数和返回值的类型,但就是管不住参数的数量,比如无参或者多参的函数还能通过类型检查,这确实挺头疼的。
问题根源分析
你之前定义的SupplierFunction里有两个重载:一个是单参数签名,另一个是剩余参数签名((...arg0: ...)),这就给了TypeScript“钻空子”的机会,无参或多参函数也能匹配到这个类型。咱们得把类型约束改得更严格,直接限定传入的函数必须是只接受单个参数的函数。
解决方案代码
先调整类型定义,新增一个工具类型来精准判断函数参数数量,再修改装饰器的泛型约束:
// 工具类型:严格校验函数是否恰好接受一个参数 type ExactlyOneArgFn<T> = T extends (arg: infer P) => infer R ? unknown[] extends P[] ? never // 排除无参函数(无参时infer P会得到unknown) : P[] extends [unknown] ? (arg: P) => R // 仅保留恰好一个参数的函数类型 : never // 排除多参函数 : never; // 保留你原来的Constraints类型,用于过滤特殊类型 type Constraints<C> = C extends | Function | PropertyDescriptor | Document | Omit<RegExp, "lastIndex"> | void | never ? never : C; // 重新定义装饰器,泛型直接约束为恰好单参数的函数 export default function decorateFunction<F extends ExactlyOneArgFn<F>>(fn: F) { return async (payload: Parameters<F>[0]) => { const result: ReturnType<F> = await new Promise((resolve) => { resolve(fn(payload)); }); // 对返回值应用Constraints约束 return result as Constraints<ReturnType<F>>; }; }
代码逻辑解释
ExactlyOneArgFn工具类型的核心逻辑:- 先判断目标类型是不是函数,提取它的参数类型
P和返回值类型R - 如果是无参函数(
P为unknown),直接返回never排除 - 如果是多参函数(
P的数组形式不是[unknown]),同样返回never排除 - 只有恰好单参数的函数会保留原类型
- 先判断目标类型是不是函数,提取它的参数类型
测试案例验证
现在你的测试案例会得到预期的类型检查结果:
应该通过的案例(无类型错误)
// Should OK const f1 = decorateFunction((payload) => { return 2; }); const f2 = decorateFunction((payload: string) => { return false; }); const f3 = decorateFunction(({ payload }) => { return (1 + 2).toString(); }); const f4 = decorateFunction(({ payload }: { payload: boolean }) => { return null; });
应该报错的案例(现在会正确触发类型错误)
// Should Error const e1 = decorateFunction(); // 错误:缺少必填的函数参数 const e2 = decorateFunction(() => {}); // 错误:无参函数不符合约束 const e3 = decorateFunction(""); // 错误:传入的不是函数类型 const e4 = decorateFunction(() => true); // 错误:无参函数不符合约束 const e6 = decorateFunction((payload1, payload2) => false); // 错误:多参函数不符合约束
复用函数的场景(依然保持原有类型校验)
// Should OK const r1 = f1("anything"); // Promise<number> const r2 = f2("string"); // Promise<boolean> const r3 = f3({ payload: "anything" }); // Promise<string> const r4 = f4({ payload: true }); // Promise<null> // Should Error const re1 = f1("any").then((e) => e.charAt(1)); // 错误:number类型没有charAt方法 const re2 = f2(1); // 错误:参数类型应为string const re3 = f3("parameter should be object"); // 错误:参数应为包含payload的对象 const re4 = f4({ nothing: "property invalid" }); // 错误:缺少必填的payload属性
备注:内容来源于stack exchange,提问作者Azvya Erstevan
相关产品推荐
相关产品推荐

