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
相关产品推荐
相关产品推荐

