如何为带动态参数的TypeScript高阶函数添加完整类型定义?
实现思路与解决方案
当然可以实现这种类型定义!我们可以利用TypeScript的元组类型和条件类型,精准描述高阶函数(HOF)的参数约束——让回调里的最后一个参数保持必填(因为HOF会保证要么调用者传入,要么自动填充),同时让HOF返回的函数允许该参数可选。
1. 定义工具类型处理可选最后参数
首先我们需要一个工具类型,把任意参数元组的最后一个元素转为可选:
// 将元组的最后一个元素转为可选类型 type MakeLastOptional<T extends any[]> = T extends [...infer Prefix, infer Last] ? [...Prefix, Last?] : T;
这个类型通过条件类型和元组展开,提取元组的前缀部分,然后把最后一个元素标记为可选。
2. 给HOF添加泛型类型约束
接下来我们用这个工具类型来约束HOF的泛型参数:
// 假设findString是已定义的异步函数 declare function findString(): Promise<string>; const hof = <Args extends any[], R>( callback: (...args: Args) => Promise<R> ): (...args: MakeLastOptional<Args>) => Promise<R> => { return async (...args) => { // 检查最后一个参数是否是string(即调用者是否传入了可选参数) if (args.length > 0) { const lastArg = args[args.length - 1]; if (typeof lastArg === "string") { // 这里需要类型断言,因为TypeScript无法静态推断args此时符合Args类型 return callback(...args as Args); } } // 自动填充缺失的最后一个参数 const s = await findString(); return callback(...[...args, s] as Args); }; };
3. 测试类型效果
现在使用这个HOF时,TypeScript会自动推导参数约束:
// 回调中str是必填项(因为HOF会保证它存在) const fn = hof(async (bool: boolean, num: number, str: string) => { console.log(bool, num, str); return str.length; }); // 合法:可以不传最后一个str参数,HOF会自动填充 fn(true, 123); // 合法:也可以手动传入str参数 fn(false, 456, "hello"); // 编译错误:缺少前两个必填参数 // fn(); // 编译错误:参数类型不匹配 // fn("invalid", 123);
适配MySQL事务场景
针对你提到的MySQL事务管理场景,我们只需要调整类型判断逻辑,确保最后一个参数是PoolConnection类型即可:
优化类型守卫(可选但推荐)
先定义一个类型守卫函数,更严谨地判断连接类型:
import type { PoolConnection } from 'mysql2/promise'; function isPoolConnection(obj: unknown): obj is PoolConnection { return ( typeof obj === 'object' && obj !== null && 'constructor' in obj && (obj as any).constructor.name === 'PoolConnection' ); }
事务HOF的类型定义
// 假设_transactional是内部事务处理函数 declare function _transactional<T>( callback: (conn: PoolConnection) => Promise<T> ): Promise<T>; const transactional = <Args extends [...any[], PoolConnection], R>( run: (...args: Args) => Promise<R> ): (...args: MakeLastOptional<Args>) => Promise<R> => { return async (...args) => { if (args.length > 0) { const lastArg = args[args.length - 1]; // 使用类型守卫确认是连接对象 if (isPoolConnection(lastArg)) { return run(...args); // 类型守卫后无需断言 } } // 自动创建连接并执行事务 return _transactional(async (connection) => run(...[...args, connection] as Args) ); }; };
使用示例
const updateUserActive = transactional(async (userId: number, conn: PoolConnection) => { // conn在这里是必填的,因为transactional会保证它存在 await conn.execute('UPDATE users SET active = 1 WHERE id = ?', [userId]); return true; }); // 自动创建事务执行 updateUserActive(123); // 复用已有连接执行 const existingConn = await getConnection(); // 假设getConnection是获取连接的函数 updateUserActive(456, existingConn);
更严谨的类型约束(可选)
如果你想强制回调的最后一个参数必须是特定类型(比如string或PoolConnection),可以进一步约束泛型:
// 强制回调的最后一个参数是T类型 type CallbackWithLastArg<T, R> = (...args: [...any[], T]) => Promise<R>; const hof = <T, R>( callback: CallbackWithLastArg<T, R> ): (...args: MakeLastOptional<Parameters<typeof callback>>) => Promise<R> => { // 实现逻辑不变 };
这样可以避免回调的最后一个参数不是目标类型的错误。
内容的提问来源于stack exchange,提问作者sp00m
相关产品推荐
相关产品推荐

