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

如何为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)
    })
  )
);

为什么这样可行?

每次中间件包裹都会缩小处理器需要的上下文范围:

  1. 最内层处理器需要包含pid、session、project的完整上下文
  2. withProject将其转换为只需要pid的处理器(由中间件补充project)
  3. withSession再将其转换为只需要pid的处理器(由中间件补充session)
  4. withErrorLogging不改变上下文要求,最终得到的处理器仅需pid即可

这样既保留了中间件的链式调用逻辑,又让TypeScript自动完成类型推导,无需手动声明所有属性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 16:22:51