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

如何为带动态参数的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 22:28:12