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

如何基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 18:55:00