基于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
相关产品推荐
相关产品推荐

