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

基于TypeScript实现带类型约束的可复用URLBuilder路径构建问题

TypeScript 类型约束的建造者模式 URLBuilder 实现

一、构造函数传入多段路径(支持动态层级)

你之前的实现问题在于泛型约束方式不对,P extends Path 无法让TypeScript根据传入的参数自动缩小类型范围。我们可以通过定义路径层级结构,再用递归工具类型生成所有合法路径组合,来实现类型约束和自动提示:

1. 定义路径层级Schema

// 用对象嵌套定义路径层级:key是当前路径段,value是下一层路径(never表示无后续路径)
type RouteSchema = {
  fixtures: {
    statistics: never;
    lineups: never;
  };
  teams: {
    seasons: never;
    countries: never;
  };
};

2. 递归生成合法路径元组

// 递归遍历RouteSchema,生成所有合法的路径段元组
type PathTuple<T extends object> = {
  [K in keyof T]: T[K] extends never ? [K] : [K, ...PathTuple<T[K]>];
}[keyof T];

3. 实现URLBuilder类

class URLBuilder {
  private pathSegments: string[] = [];
  private queryParams: Record<string, string> = {};

  // 构造函数接受所有合法路径组合,自动触发类型提示
  constructor(...path: PathTuple<RouteSchema>) {
    this.pathSegments = [...path];
  }

  // 已完成的查询参数方法示例
  addQueryParam(key: string, value: string) {
    this.queryParams[key] = value;
    return this;
  }

  build() {
    const pathStr = this.pathSegments.join('/');
    const queryStr = new URLSearchParams(this.queryParams).toString();
    return `/${pathStr}${queryStr ? `?${queryStr}` : ''}`;
  }
}

使用示例

// 正确:fixtures下只能选statistics/lineups
const validUrl = new URLBuilder('fixtures', 'statistics').addQueryParam('date', '2024-05-01').build();

// 错误:teams下没有statistics,TS直接报错
const invalidUrl = new URLBuilder('teams', 'statistics');

这种方式支持任意层级的路径,只要扩展RouteSchema即可自动适配。

二、链式调用的路径构建方式

链式调用更符合建造者模式的简洁性,核心思路是用泛型跟踪当前路径节点,每次调用setPath后更新泛型,约束下一次的可选路径:

1. 实现链式Builder类

class ChainedURLBuilder<CurrentNode extends object = RouteSchema> {
  private pathSegments: string[] = [];
  private queryParams: Record<string, string> = {};

  // 添加当前节点下的合法路径段,返回泛型更新后的新Builder
  setPath<K extends keyof CurrentNode>(segment: K): ChainedURLBuilder<NonNullable<CurrentNode[K]>> {
    const newBuilder = new ChainedURLBuilder<NonNullable<CurrentNode[K]>>();
    newBuilder.pathSegments = [...this.pathSegments, segment as string];
    newBuilder.queryParams = {...this.queryParams};
    return newBuilder;
  }

  // 查询参数方法,保持当前泛型节点不变
  addQueryParam(key: string, value: string): ChainedURLBuilder<CurrentNode> {
    const newBuilder = new ChainedURLBuilder<CurrentNode>();
    newBuilder.pathSegments = [...this.pathSegments];
    newBuilder.queryParams = {...this.queryParams, [key]: value};
    return newBuilder;
  }

  // 路径结束后调用build生成URL
  build(): string {
    const pathStr = this.pathSegments.join('/');
    const queryStr = new URLSearchParams(this.queryParams).toString();
    return `/${pathStr}${queryStr ? `?${queryStr}` : ''}`;
  }
}

使用示例

// 链式调用,每一步都有严格的类型提示
const chainedUrl = new ChainedURLBuilder()
  .setPath('teams') // 可选值:fixtures/teams
  .setPath('seasons') // 可选值:seasons/countries
  .addQueryParam('year', '2024')
  .build();

// 错误:fixtures下没有seasons,TS报错
const invalidChainedUrl = new ChainedURLBuilder()
  .setPath('fixtures')
  .setPath('seasons');

这种方式的优势是可以逐步构建路径,每一步的可选范围都由前一步的选择决定,完全符合你的类型约束需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 22:47:07