TypeScript:生成RouteUrl类型时遇类型错误求助
TypeScript 实现路径参数替换的 RouteUrl 类型问题
已实现的路径参数提取类型
/** * 按 `/` 分割输入路径,返回路径段的联合类型。 * 使用尾递归条件类型处理长路径的性能问题 * * @example * ``` * type Segments = PathSegments<'/courses/:courseId/classes/:classId'>; * // ^? "" | "courses" | ":courseId" | "classes" | ":classId" * ``` */ type PathSegments< Path extends string, Accumulator extends string = never > = Path extends `${infer Left}/${infer Right}` ? PathSegments<Right, Accumulator | Left> : Accumulator | Path; /** * 创建一个对象类型: * - 键为 PathSegments 中以 `:` 开头的路径段(去掉:`) * - 值为 string 类型 * * 使用键重映射过滤掉不以:`开头的路径段 * * @example 1 - 路径无动态参数 * ``` * type Params = RouteParams<'/courses/classes'>; * // ^? {} * ``` * * @example 2 - 路径含动态参数 * ``` * type Params = RouteParams<'/courses/:courseId/classes/:classId'>; * // ^? { courseId: string; classId: string } * ``` */ type RouteParams<Path extends string> = { [PathSegment in PathSegments<Path> as PathSegment extends `:${infer RouteParam}` ? RouteParam : never]: string; };
尝试实现 RouteUrl 类型及错误
尝试编写RouteUrl类型将路径中的参数替换为给定的Params值,但出现类型错误:
type RouteUrl< Path extends string, Params extends RouteParams<Path> > = Path extends `${infer Left}/:${infer Param}/${infer Right}` ? `${Left}/${Params[Param]}/${RouteUrl<Right, Params extends RouteParams<Right> ? Params : never>}` : Path extends `${infer Left}/:${infer Param}` ? `${Left}/${Params[Param]}` : Path;
测试代码:
type Url = RouteUrl<'/courses/:courseId/classes/:classId', { courseId: '123'; classId: '456' }>;
报错信息:
Type 'Params[Param]' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
Type 'Params[string]' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
Type 'RouteParams[string]' is not assignable to type 'string | number | bigint | boolean | null | undefined'.ts(2322)
Type 'Param' cannot be used to index type 'Params'.ts(2536)
替代思路:遍历参数键逐个替换
希望参考运行时的替换逻辑,在类型层面遍历Params的键,将路径中的:${key}替换为对应的值。已有字符串替换泛型:
export type Replace< Str extends string, Search extends string, Replacement extends string > = Str extends `${infer Prefix}${Search}${infer Suffix}` ? `${Prefix}${Replacement}${Suffix}` : Str;
但不知道如何在类型层面遍历对象的键完成递归替换。
解决方案:递归遍历参数键实现替换
通过递归遍历Params的键联合类型,逐个调用Replace类型完成替换,直到所有参数处理完毕:
export type Replace< Str extends string, Search extends string, Replacement extends string > = Str extends `${infer Prefix}${Search}${infer Suffix}` ? `${Prefix}${Replacement}${Suffix}` : Str; type RouteUrl< Path extends string, Params extends Record<string, string> > = keyof Params extends never ? Path : { [K in keyof Params]: RouteUrl<Replace<Path, `:${K & string}`, Params[K]>, Omit<Params, K>>; }[keyof Params];
测试验证
// 多参数路径测试 type Url1 = RouteUrl<'/courses/:courseId/classes/:classId', { courseId: '123'; classId: '456' }>; // ^? "/courses/123/classes/456" // 无参数路径测试 type Url2 = RouteUrl<'/courses/classes', {}>; // ^? "/courses/classes" // 单参数路径测试 type Url3 = RouteUrl<'/user/:userId', { userId: '789' }>; // ^? "/user/789"
逻辑说明
- 终止条件:当
keyof Params为never(即Params为空对象),直接返回原路径。 - 递归替换:遍历
Params的每个键K,先替换路径中的:${K}为对应值,再递归处理剩余参数(通过Omit<Params, K>移除已处理的键)。 - 结果收敛:利用映射类型的索引访问,从所有可能的替换分支中提取最终的单一字符串结果。
这个实现完美匹配运行时的循环替换逻辑,同时解决了原实现中TypeScript无法推断Param与Params键关联的问题。
内容的提问来源于stack exchange,提问作者Vishal
相关产品推荐
相关产品推荐

