如何基于Paths类型与键数组创建类型安全的路径段合并函数?
类型安全的URL路径合并函数实现问题
给定以下TypeScript类型定义,用于表示按路径段拆分的多个URL路径:
type PathSegment = { path: string; children: Paths; } type Paths = Record<string, PathSegment | string>;
对应的示例对象如下:
const paths: Paths = { home: "/", profile: "/profile", settings: { path: "/settings", children: { general: "/general", account: "/account", } } }
需求是创建一个类型安全的函数mergePathSegments,通过传入待合并路径的键数组来拼接路径段。例如调用:
const accountSettingsPath = mergePathSegments(paths, ["settings", "account"]); console.log(accountSettingsPath); // 输出 "/settings/account"
要求传入的键数组必须在编译时就能校验合法性:
- 避免键名拼写错误
- 数组长度不能超过当前层级可用的子键数量
请问能否实现该功能?
实现方案
完全可以实现,核心是利用TypeScript的递归条件类型和模板字面量类型,在编译时追踪路径的层级结构,对传入的键数组做严格校验,同时在运行时正确拼接路径。
1. 定义递归辅助类型,提取合法路径层级
首先定义几个辅助类型,用来递归提取Paths结构中每一层的合法键,以及对应的路径拼接结果:
// 提取当前层级的所有合法键 type PathKeys<T extends Paths> = keyof T; // 递归生成所有合法的路径键数组组合 type ValidPathTuples<T extends Paths> = { [K in PathKeys<T>]: T[K] extends PathSegment ? [K] | [K, ...ValidPathTuples<T[K]["children"]>] : [K] }[PathKeys<T>]; // 根据路径键数组,编译时计算拼接后的路径字符串类型 type MergedPath<T extends Paths, P extends ValidPathTuples<T>> = P extends [infer First extends keyof T, ...infer Rest] ? T[First] extends string ? T[First] : `${T[First]["path"]}${Rest extends ValidPathTuples<T[First]["children"]> ? `/${MergedPath<T[First]["children"], Rest>}` : ""}` : "";
2. 实现类型安全的合并函数
基于上面的辅助类型,编写函数实现,通过泛型约束确保传入的键数组合法:
function mergePathSegments<T extends Paths, P extends ValidPathTuples<T>>( paths: T, pathKeys: P ): MergedPath<T, P> { let result = ""; let currentNode: Paths | PathSegment = paths; for (const key of pathKeys) { if (typeof currentNode === "object" && !Array.isArray(currentNode)) { const segment = currentNode[key]; if (typeof segment === "string") { // 处理叶子节点路径,移除重复的开头斜杠 const cleanPath = segment.replace(/^\//, ""); result += result ? `/${cleanPath}` : cleanPath; } else { // 处理带子节点的路径段 const cleanPath = segment.path.replace(/^\//, ""); result += result ? `/${cleanPath}` : cleanPath; currentNode = segment.children; } } } // 确保最终路径以斜杠开头 return (result.startsWith("/") ? result : `/${result}`) as MergedPath<T, P>; }
3. 验证效果
使用示例对象测试,注意要给对象加上as const断言,让TypeScript能精确推导每个路径的字面量类型:
const paths = { home: "/", profile: "/profile", settings: { path: "/settings", children: { general: "/general", account: "/account", } } } as const; // 合法调用:编译通过,返回类型为"/settings/account" const accountSettingsPath = mergePathSegments(paths, ["settings", "account"]); console.log(accountSettingsPath); // 输出 "/settings/account" // 错误调用1:键名拼写错误,编译直接报错 // const wrongPath = mergePathSegments(paths, ["setting", "account"]); // 错误调用2:路径层级超出可用子键,编译直接报错 // const invalidPath = mergePathSegments(paths, ["settings", "account", "sub"]);
关键说明
as const断言是实现精确类型推导的前提,它会让TypeScript把对象的属性值识别为字面量类型,而非宽泛的string类型。- 递归类型
ValidPathTuples会生成所有合法的路径键数组组合,确保传入的数组只能是这些合法组合之一,从根源避免拼写错误和层级越界。 MergedPath类型会在编译时直接计算出拼接后的路径字符串类型,让函数返回值的类型完全确定,进一步增强类型安全性。
内容的提问来源于stack exchange,提问作者sookie
相关产品推荐
相关产品推荐

