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

如何不使用自定义编译器,用TypeScript从路由字符串自动推导URL参数类型?

可以实现,无需使用ttypescript或自定义编译插件,仅需TypeScript 4.1及以上版本,用其原生支持的模板字面量类型、递归条件类型即可达到预期效果。

实现步骤

1. 定义路径参数提取工具类型

先编写通用工具类型,用于从路由字符串中自动提取所有:开头的路径参数,生成对应的params类型:

// 提取路由中的路径部分(去掉前面的GET/POST等请求方法)
type ExtractPath<RouteStr extends string> = RouteStr extends `${string} ${infer Path}` ? Path : RouteStr;

// 递归解析路径,提取所有:xxx参数,生成params类型
type PathParams<Path extends string> = 
  // 匹配路径中间的:参数/xxx格式
  Path extends `${string}:${infer Param}/${infer Rest}` 
    ? { [K in Param | keyof PathParams<Rest>]: string }
    // 匹配路径末尾的:参数格式
    : Path extends `${string}:${infer Param}`
      ? { [K in Param]: string }
      // 没有参数的情况返回空对象
      : Record<string, never>;

2. 增强原插件的类型定义

基于typed-fastify原有的Schema和Service类型,封装一层自动注入params的增强类型:

import { Schema, Service as OriginalService } from '@coobaha/typed-fastify';

// 遍历所有路由,自动给每个路由补充params类型
type EnhancedPaths<RawPaths extends Record<string, any>> = {
  [RouteKey in keyof RawPaths]: Omit<RawPaths[RouteKey], 'request'> & {
    request?: Omit<RawPaths[RouteKey]['request'], 'params'> & {
      // 如果用户手动定义了params会优先使用手动定义的,否则用自动生成的
      params?: RawPaths[RouteKey]['request']['params'] extends Record<string, any> 
        ? RawPaths[RouteKey]['request']['params'] 
        : PathParams<ExtractPath<RouteKey & string>>
    }
  }
}

// 对外暴露的增强Service类型,直接替换原有Service使用即可
type EnhancedService<RawSchema extends { paths: Record<string, any> }> = 
  OriginalService<{
    paths: EnhancedPaths<RawSchema['paths']>
  } & Omit<Schema, 'paths'>>

使用示例

和预期写法完全一致,无需手动定义params即可获得正确的类型校验:

interface ExampleSchema {
  paths: {
    'GET /test/:testId': {
      // 仅需定义其他属性,无需显式写params
      response: {
        200: {
          content: {
            'application/json': {
              id: string
            }
          }
        }
      }
    };
    'POST /test/:testId/:userId': {}
  };
}

const exampleService: EnhancedService<ExampleSchema> = {
  'GET /test/:testId': (req, reply) => {
    const testId = req.params.testId // 类型校验通过,类型为string
    const testName = req.params.testName // 类型校验失败,提示属性不存在
  },
  'POST /test/:testId/:userId': (req, reply) => {
    const { testId, userId } = req.params // 两个参数都能正确推断为string类型
  }
};

兼容说明

如果部分参数需要自定义类型(比如数字类型参数),直接手动定义params即可,手动定义的优先级高于自动生成的类型,完全兼容原有写法。


内容的提问来源于stack exchange,提问作者Valerio Leo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 12:06:04