如何为Serverless中间件实现TypeScript类型继承?
解决方案:自动推导TypeScript中间件上下文类型
核心问题分析
你之前的Middleware类型定义没有正确体现「中间件补充上下文属性」的逻辑,导致TypeScript无法自动推导组合后的上下文类型,必须手动声明所有属性。
修正后的类型定义
重新定义Middleware类型,明确区分输入处理器需要的上下文和输出处理器需要的上下文:
// 基础上下文:所有请求都具备的通用属性 type BaseContext = { prop1: string; }; // 组合上下文:基础上下文 + 自定义属性 type Context<T = {}> = BaseContext & T; // 处理器类型:接收上下文,返回Promise type Handler<T = {}> = (ctx: Context<T>) => Promise<void>; // 中间件类型: // - RequiredProps:中间件要求输入上下文必须包含的属性 // - AddedProps:中间件会给上下文新增的属性 // 作用:把需要「T + AddedProps」的处理器,转换成只需要「T(满足RequiredProps)」的处理器 type Middleware<RequiredProps, AddedProps> = <T extends RequiredProps>( handler: Handler<T & AddedProps> ) => Handler<T>;
实现类型正确的中间件
基于上面的类型定义,实现各个中间件:
// 错误日志中间件:不要求前置属性,也不新增属性 const withErrorLogging: Middleware<unknown, {}> = (handler) => { return async (ctx) => { try { return await handler(ctx); } catch (e) { // 自定义错误处理逻辑 console.error("请求出错:", e); } }; }; // 会话中间件:不要求前置属性,新增session属性 const withSession: Middleware<{}, { session: "session" }> = (handler) => { return (ctx) => { // 这里可以替换为实际的会话获取逻辑(比如从请求头解析) return handler({ ...ctx, session: "session" }); }; }; // 项目中间件:要求上下文有pid属性,新增project属性 type Project = { id: number; name: string }; const getProjectById = async (pid: number): Promise<Project | null> => { // 模拟数据库查询逻辑 return { id: pid, name: "测试项目" }; }; const withProject: Middleware<{ pid: number }, { project: Project }> = (handler) => { return async (ctx) => { const project = await getProjectById(ctx.pid); if (!project) { // 404逻辑(比如返回错误响应) console.log("项目不存在"); return; } return handler({ ...ctx, project }); }; };
最终使用方式
现在只需定义路由自带的参数,TypeScript会自动推导所有中间件注入的属性:
// 仅定义路由本身的参数 type RouteProps = { pid: number }; export const handler = withErrorLogging( withSession( withProject(async (ctx) => { // ctx类型被自动推导为:Context<RouteProps & { session: "session" } & { project: Project }> ctx.pid; // ✅ 类型:number ctx.session; // ✅ 类型:"session" ctx.project; // ✅ 类型:Project ctx.prop1; // ✅ 类型:string(来自BaseContext) }) ) );
为什么这样可行?
每次中间件包裹都会缩小处理器需要的上下文范围:
- 最内层处理器需要包含
pid、session、project的完整上下文 withProject将其转换为只需要pid的处理器(由中间件补充project)withSession再将其转换为只需要pid的处理器(由中间件补充session)withErrorLogging不改变上下文要求,最终得到的处理器仅需pid即可
这样既保留了中间件的链式调用逻辑,又让TypeScript自动完成类型推导,无需手动声明所有属性。
内容的提问来源于stack exchange,提问作者Martinho Santos
相关产品推荐
相关产品推荐

