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

TypeScript中如何基于可变断言参数传递类型至回调?

问题描述

我正在为项目创建Express路由处理器生成函数makeHandler,希望在传入路由处理器回调前,先传入任意数量的断言参数,示例如下:

const myHandler = makeHandler(assertion1(), assertion2(), (data, req, res, next) => {
  // data应为所有断言执行结果组成的数组
});

单独测试类型时能得到预期效果:

// 为简洁起见省略express命名空间

type Assertion<T = unknown> = (req: express.Request) => T;
type ReturnTypes<A extends ReadonlyArray<Assertion>> = {
  [K in keyof A]: ReturnType<A[K]>;
};

function assertion1<T extends object>(arg: T) {
  return () => arg
}

function assertion2() {
  return () => "yes"
}

const a = assertion1({ something: "yes" })
const b = assertion2()

// 此类型符合预期:为[{ something: string }, string]
type d = ReturnTypes<[typeof a, typeof b]>

但适配为makeHandler的可变参数版本时,回调中的data类型变为unknown[],而非预期类型:

// 省略makeHandler的逻辑实现

declare function makeHandler<
 Assertions extends Assertion<unknown>[]
>(...assertionsAndCb: [...Assertions, HandlerCb<ReturnTypes<Assertions>>]): void

// 此处data类型不正确,变为unknown[],而非上述type d的类型
makeHandler(assertion1({ hey: "what" }), assertion2(), (data, req) => {
  return { response: {} }
})

参考过类似zip函数的实现思路编写当前函数,但仍无法让类型正确传递,请问遗漏了什么?比如是否存在泛型推断问题?


解决方案

问题出在泛型约束Assertions extends Assertion<unknown>[]上,这个约束会让TypeScript把Assertions推断为宽泛的Assertion<unknown>[]类型,而非具体的元组类型,导致ReturnTypes<Assertions>只能解析为unknown[]。

要解决这个问题,需要引导TypeScript将断言参数推断为具体的元组类型,而非宽泛的数组类型,以下是两种可行方案:

方案一:调整泛型约束为元组友好形式

type Assertion<T = unknown> = (req: express.Request) => T;
type ReturnTypes<A extends readonly Assertion[]> = {
  [K in keyof A]: A[K] extends Assertion<infer T> ? T : never;
};
type HandlerCb<T> = (data: T, req: express.Request) => { response: {} };

declare function makeHandler<
  Assertions extends readonly Assertion[]
>(...assertionsAndCb: [...Assertions, HandlerCb<ReturnTypes<Assertions>>]): void

// 现在data的类型会正确推断为[{ hey: string }, string]
makeHandler(assertion1({ hey: "what" }), assertion2(), (data, req) => {
  return { response: {} }
})

方案二:基于返回值元组反向推导(更直观)

type Assertion<T = unknown> = (req: express.Request) => T;
type HandlerCb<T> = (data: T, req: express.Request) => { response: {} };

declare function makeHandler<
  T extends unknown[]
>(...args: [...assertions: { [K in keyof T]: Assertion<T[K]> }, HandlerCb<T>]): void

// data类型正确推断为[{ hey: string }, string]
makeHandler(assertion1({ hey: "what" }), assertion2(), (data, req) => {
  return { response: {} }
})

关键原因

  • 使用extends Assertion<unknown>[]时,TypeScript倾向于将数组参数推断为宽泛的数组类型,而非精确元组。
  • 改用readonly Assertion[]约束,或者基于返回值元组T反向约束断言类型,能引导TypeScript推断出精确的元组结构,从而让data的类型正确传递。

内容的提问来源于stack exchange,提问作者josh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 02:45:20