TypeScript如何定义函数数组实现CTX类型依次扩展前序返回值
TypeScript 累加上下文类型的中间件数组实现方案
核心通过递归类型推导,自动约束每一步中间件的入参上下文,同时累加所有中间件的返回值到最终上下文类型中。
第一步:定义基础工具类型
// 基础中间件类型约束:接收当前上下文,返回需要合并到上下文的新属性 type Middleware<CurrentCtx, Return extends Record<string, any>> = (ctx: CurrentCtx) => Return; // 递归约束中间件数组:每一项的入参自动包含之前所有中间件的返回值 type MiddlewareArray<BaseCtx, Middlewares extends any[], AccuReturn = {}> = Middlewares extends [infer FirstMiddleware, ...infer RestMiddlewares] ? FirstMiddleware extends Middleware<BaseCtx & AccuReturn, infer CurrReturn> ? [FirstMiddleware, ...MiddlewareArray<BaseCtx, RestMiddlewares, AccuReturn & CurrReturn>] : never : []; // 提取中间件数组所有返回值的合并类型,用于生成最终上下文 type MergeMiddlewareReturns<BaseCtx, Middlewares extends any[]> = Middlewares extends [infer FirstMiddleware, ...infer RestMiddlewares] ? FirstMiddleware extends Middleware<BaseCtx & infer _, infer CurrReturn> ? CurrReturn & MergeMiddlewareReturns<BaseCtx, RestMiddlewares> : never : {};
第二步:封装符合场景的Api实例类型
// 自定义基础请求上下文,可根据业务扩展 type BaseReqCtx = { req: Request; res: Response; }; // Api实例类型,泛型存储全局累加的上下文类型 type ApiInstance<GlobalCtx extends {} = {}> = { // use方法注册全局中间件,返回新的实例,全局上下文自动累加 use: <Middlewares extends any[]>(middlewares: MiddlewareArray<BaseReqCtx, Middlewares>) => ApiInstance<GlobalCtx & MergeMiddlewareReturns<BaseReqCtx, Middlewares>>; // get方法注册路由中间件和处理函数,最终处理函数的ctx自动包含所有中间件返回值 get: <Middlewares extends any[]>( middlewares: MiddlewareArray<BaseReqCtx & GlobalCtx, Middlewares>, handler: (req: Request, res: Response, ctx: BaseReqCtx & GlobalCtx & MergeMiddlewareReturns<BaseReqCtx & GlobalCtx, Middlewares>) => void ) => ApiInstance<GlobalCtx>; }; // 声明api工厂函数 declare function api(): ApiInstance;
验证示例
// 示例业务类型,需根据实际业务自行定义 interface IORedis {} interface IUser {} interface IGuild {} // 示例中间件定义 declare const redis: (ctx: BaseReqCtx) => { redis: IORedis }; declare const loggedIn: (ctx: BaseReqCtx & { redis: IORedis }) => { token: string, session: string }; declare const getUser: (ctx: BaseReqCtx & { redis: IORedis, token: string, session: string }) => { user: IUser }; declare const getGuild: (ctx: BaseReqCtx & { redis: IORedis, token: string, session: string, user: IUser }) => { guild: IGuild }; // 实际调用测试 export default api() .use([redis, loggedIn]) .get([getUser, getGuild], (req,res,ctx) => { // 此时ctx会自动推导出所有属性,无需手动声明类型 console.log(ctx.redis) console.log(ctx.token) console.log(ctx.session) console.log(ctx.user) console.log(ctx.guild) })
补充说明
- 中间件返回值需要是对象类型,才能和上下文做交叉合并,不需要返回额外属性时返回
{}即可。 - 其他请求方法如post、put等可以参考get方法的类型定义扩展。
内容的提问来源于stack exchange,提问作者Jeroeno_Boy
相关产品推荐
相关产品推荐

