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

