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

如何从Express Handler接口属性值隐式推断泛型类型?

实现Zod验证器与Express Handler的类型隐式推断

你的需求完全可行,问题出在原接口的泛型设计上——直接在接口层面声明泛型参数时,TypeScript无法从validators.body的传入值反向推断出Body类型。下面是调整后的实现方案:

调整后的类型定义

首先重新定义Handler类型,将泛型参数与validators的结构绑定,再通过一个辅助函数来自动推断类型:

import { RequestHandler } from 'express';
import { ZodObject, z } from 'zod';

// 定义Handler的核心结构,泛型对应各验证器的类型
type Handler<
  Body extends ZodObject<any> | undefined = undefined,
  Query extends ZodObject<any> | undefined = undefined,
  Params extends ZodObject<any> | undefined = undefined
> = {
  path?: string;
  method: "get" | "post" | "put" | "patch" | "delete";
  validators?: {
    body?: Body;
    query?: Query;
    params?: Params;
  };
  // 根据验证器类型推断RequestHandler的泛型参数
  handler: RequestHandler<
    Params extends ZodObject<any> ? z.infer<Params> : {},
    any,
    Body extends ZodObject<any> ? z.infer<Body> : {},
    Query extends ZodObject<any> ? z.infer<Query> : {}
  >;
};

// 辅助函数,用于自动推断泛型类型
function createHandler<
  Body extends ZodObject<any> | undefined = undefined,
  Query extends ZodObject<any> | undefined = undefined,
  Params extends ZodObject<any> | undefined = undefined
>(handler: Handler<Body, Query, Params>): Handler<Body, Query, Params> {
  return handler;
}

使用示例

现在你可以直接传入validators.body,TypeScript会自动推断出handler中req.body的类型:

const testHandler = createHandler({
  method: 'post',
  validators: {
    body: z.object({ x: z.string() })
  },
  handler: (req, res) => {
    // req.body自动推断为 { x: string }
    console.log(req.body.x); // 类型安全,不会报错
    res.send('ok');
  }
});

关键原理

  • 原接口的泛型参数是声明式的,需要显式指定;而辅助函数createHandler利用了TypeScript的上下文类型推断,可以从传入的handler对象中自动提取validators里的Zod对象类型,进而推导RequestHandler的泛型参数。
  • 为Body、Query、Params添加了undefined的默认值,这样当不需要对应验证器时,不会强制要求传入泛型参数。

额外优化(可选)

如果不想使用辅助函数,也可以通过条件类型直接让接口支持推断,但这种方式在直接创建对象时需要TypeScript 4.7+的支持,且写法稍繁琐:

type Handler<TValidators extends { body?: ZodObject<any>, query?: ZodObject<any>, params?: ZodObject<any> } = {}> = {
  path?: string;
  method: "get" | "post" | "put" | "patch" | "delete";
  validators?: TValidators;
  handler: RequestHandler<
    TValidators['params'] extends ZodObject<any> ? z.infer<TValidators['params']> : {},
    any,
    TValidators['body'] extends ZodObject<any> ? z.infer<TValidators['body']> : {},
    TValidators['query'] extends ZodObject<any> ? z.infer<TValidators['query']> : {}
  >;
};

// 使用方式
const testHandler: Handler<{ body: typeof z.object({ x: z.string() }) }> = {
  method: 'post',
  validators: {
    body: z.object({ x: z.string() })
  },
  handler: (req, res) => {
    console.log(req.body.x);
    res.send('ok');
  }
};

不过这种方式还是需要显式指定泛型参数,不如辅助函数的体验好,推荐使用第一种方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 22:03:30