首页 > 解决方案 > 在 TypeScript 中将递归数组/对象作为展平对象类型返回

问题描述

我正在尝试编写一个函数,该函数采用递归对象/数组,其中每个节点都有一个“名称”,以及可选的“孩子”。然后我想有一个函数,它采用这种递归结构并返回一个类型安全的对象,其中递归出现的“名称”是键,节点是值;因此,如果您尝试访问不存在的密钥,它会在编译期间为您出错。

到目前为止,我能够以这种方式识别顶级名称(名称“a”和“b”),以便flat将其识别为Record<"a" | "b", RouteConfigItem<"a" | "b">>.

type RouteConfigItem<Keys> = {
    name: Keys;
    path: string;
    children?: Array<RouteConfigItem<Keys>>;
}

type RouteConfig<Keys> = RouteConfigItem<Keys>[];

function getFlat<Keys>(routeConfig: RouteConfig<Keys>): Record<Keys, RouteConfigItem<Keys>> {
    // Implementation doesn't matter.
    return routeConfig as Record<Keys, RouteConfigItem<Keys>>;
}

const flat = getFlat([{
    name: 'a',
    path: 'a',
}, {
    name: 'b',
    path: 'b',
    children: [{
        name: 'c',
        path: 'c',
    }]
}] as const);

但是我怎样才能让它也看到非顶级名称呢?只关心类型,而不关心 的主体中的实现getFlat()我希望flat被识别为 Record<"a" | "b" | "c", RouteConfigItem<"a" | "b" | "c">>.

顺便说一句,代码示例在我的 WebStorm 中似乎对我有用,但在 typescriptlang.org/play 中似乎不起作用,所以我没有包含这样的链接。

标签: typescriptrecursion

解决方案


我已经简化了一点RouteConfig定义:

type RouteConfig = {
    name: string, 
    path: string, 
    children?: ReadonlyArray<RouteConfig> 
};

现在要提取密钥,您可以:

type ExtractKeys<R extends RouteConfig> = R extends { children: ReadonlyArray<RouteConfig> }
    ? R['name'] | ExtractKeys<R['children'][number]>
    : R['name'];

函数签名将是:

function getFlat<R extends RouteConfig>(routeConfig: readonly R[]):
    Record<ExtractKeys<R>, RouteConfig> {

    // Implementation doesn't matter.
    return routeConfig as any;
}

const flat = getFlat([{
    name: 'a',
    path: 'a',
}, {
    name: 'b',
    path: 'b',
    children: [{
        name: 'c',
        path: 'c',
    }]
}] as const); // Record<"a" | "b" | "c", RouteConfig>

操场


推荐阅读