TypeScript路由参数校验函数类型收窄问题及优化咨询
优化方案:让校验函数支持类型收窄
核心思路是通过泛型+类型映射,让校验函数返回包含校验后值的结果,同时为TypeScript提供明确的类型信息。
1. 定义类型映射与结果类型
先建立字符串类型标识到TS原生类型的映射,同时定义统一的校验结果类型:
type QueryValue = string | number | boolean; // 根据实际场景调整 type TypeMap = { string: string; number: number; }; type ValidationResult<T> = | { valid: true; value: T } // 校验成功时返回收窄后的类型值 | { valid: false; error: ReturnType<typeof createError> }; // 校验失败时返回错误
2. 重构泛型校验函数
修改函数签名,关联传入的type参数与返回值类型,分分支处理不同类型的校验:
import createError from '你的createError路径'; import isEnumKey from './你的辅助函数路径'; export default function routeParamValidate<T extends keyof TypeMap | Record<string, unknown>>( param: QueryValue | QueryValue[], paramName: string, type: T, rules: Array<(param: T extends Record<string, unknown> ? T[keyof T] : TypeMap[T]) => { valid: boolean; errorMessage: string } | boolean> | null = null ): ValidationResult<T extends keyof TypeMap ? TypeMap[T] : T[keyof T]> { // 处理枚举类型 if (typeof type === 'object' && type !== null) { if (!isEnumKey(type)(param)) { return { valid: false, error: createError({ statusCode: 400, statusMessage: `Invalid "${paramName}" query parameter. The value must be one of the supported values.`, }) } as ValidationResult<never>; } const validatedParam = param as T[keyof T]; // 执行自定义规则校验 if (rules) { for (const rule of rules) { const result = rule(validatedParam); if ((typeof result === 'boolean' && !result) || (typeof result === 'object' && !result.valid)) { return { valid: false, error: createError({ statusCode: 400, statusMessage: typeof result === 'object' ? result.errorMessage : `Invalid "${paramName}" query parameter. Rule validation failed.`, }) } as ValidationResult<never>; } } } return { valid: true, value: validatedParam }; } // 处理string类型 if (type === 'string') { if (typeof param !== 'string') { return { valid: false, error: createError({ statusCode: 400, statusMessage: `Invalid "${paramName}" query parameter. The value must be a string.`, }) } as ValidationResult<never>; } const validatedParam = param as TypeMap['string']; if (rules) { for (const rule of rules) { const result = rule(validatedParam); if ((typeof result === 'boolean' && !result) || (typeof result === 'object' && !result.valid)) { return { valid: false, error: createError({ statusCode: 400, statusMessage: typeof result === 'object' ? result.errorMessage : `Invalid "${paramName}" query parameter. Rule validation failed.`, }) } as ValidationResult<never>; } } } return { valid: true, value: validatedParam }; } // 处理number类型 if (type === 'number') { if (typeof param !== 'string' || isNaN(parseInt(param))) { return { valid: false, error: createError({ statusCode: 400, statusMessage: `Invalid "${paramName}" query parameter. The value must be a number.`, }) } as ValidationResult<never>; } const validatedParam = parseInt(param) as TypeMap['number']; if (rules) { for (const rule of rules) { const result = rule(validatedParam); if ((typeof result === 'boolean' && !result) || (typeof result === 'object' && !result.valid)) { return { valid: false, error: createError({ statusCode: 400, statusMessage: typeof result === 'object' ? result.errorMessage : `Invalid "${paramName}" query parameter. Rule validation failed.`, }) } as ValidationResult<never>; } } } return { valid: true, value: validatedParam }; } // 默认错误分支 return { valid: false, error: createError({ statusCode: 400, statusMessage: `Invalid "${paramName}" query parameter. Unsupported type.`, }) } as ValidationResult<never>; }
3. 优化后的使用方式
现在校验成功后,validation.value会自动被收窄为目标类型:
const queryOffset: QueryValue | QueryValue[] = '10'; const validation = routeParamValidate(queryOffset, 'offset', 'number'); if (!validation.valid) { return validation.error; } // validation.value 类型为number,支持类型安全操作 console.log(validation.value.toFixed(2));
关键优化点
- 用泛型关联
type参数与返回值的类型,让TS能推导校验后的类型 - 校验成功时返回包含收窄后值的对象,替代原参数的类型修改(原参数类型不可变,TS无法直接收窄)
- 为自定义规则传入已收窄类型的参数,保证规则函数的类型安全
TypeScript进阶学习资源
- TypeScript官方文档高级类型章节:系统学习泛型、类型谓词、条件类型、映射类型等核心进阶概念
- 《TypeScript Deep Dive》:深入讲解TypeScript类型系统的底层逻辑与设计思路
- TypeScript官方GitHub示例库:包含大量类型系统实战用法与最佳实践
- 社区核心贡献者博客:关注TypeScript核心维护者的技术文章,了解类型系统的前沿用法
内容的提问来源于stack exchange,提问作者Matej
相关产品推荐
相关产品推荐

