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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 13:37:02