首页 > 解决方案 > 给定一个对象数组,是否可以泛化一个函数以给出给定键值的字符串文字联合?

问题描述

鉴于此参考代码:

const stuff = [
  { name: "Rock", children: [{ name: "Pebbles", children: undefined }] },
  { name: "Tree" },
  { name: "Sun" },
] as const

// Evaluates to `type Names = "Rock" | "Pebbles" | "Tree" | "Sun"`
type Names = typeof stuff[number]["name"] | typeof stuff[0]["children"][0]["name"]

是否可以概括 Names 类型,以便我可以让它给我所有name属性值的字符串文字联合,无论它在对象中的什么位置?

标签: typescript

解决方案


这可以在条件映射类型和一些递归的帮助下实现:

type Names<T> = T extends readonly unknown[] ?
    T[number] extends infer O
        ? O extends object
            ? {
                [K in keyof O]: 'name' extends K 
                    ? O[K] 
                    : Names<O[K]>
            }[keyof O]
            : never
        : never
    : never

游乐场链接


首先我们检查T类型是否为数组类型:

 T extends readonly unknown[] ?

接下来我们介绍类型变量O

T[number] extends infer O

这完全是修饰步骤,因为我不想在现在出现在类型中的T[number]任何地方重复。O由于条件类型的分布性质,O现在每次迭代都有每个 迭代数组元素的类型。

检查是否O实际上是一个对象:

? O extends object

最后是主要部分。映射[keyof O]类型通过查找立即减少为联合:

{ 
    [K in keyof O]: 'name' extends K ? O['name'] : Names<O[K]>
}[keyof O]

这个本质上迭代O对象的所有键,如果键值是nameget 的类型,O[K]否则尝试nameO[K].

[keyof O]如果我们删除查找,它可能有助于掌握内部发生的事情:

type Names<T> = T extends readonly unknown[] ?
    T[number] extends infer O
        ? O extends object
            ? {
                [K in keyof O]: 'name' extends K ? O[K] : Names<O[K]>
            }
            : never
        : never
    : never

const stuff = [
  { name: "Rock", children: [{ name: "Pebbles", children: undefined }] },
  { name: "Tree" },
  { name: "Sun" },
] as const

/*
type N = {
    readonly name: "Rock";
    readonly children: {
        readonly name: "Pebbles";
        readonly children: never;
    };
} | {
    readonly name: "Tree";
} | {
    readonly name: "Sun";
}
*/
type N = Names<typeof stuff>

游乐场链接

当我们[keyof O]查找每个映射对象时,我们得到:

type N = {
    readonly name: "Rock";
    readonly children: {
        readonly name: "Pebbles";
        readonly children: never;
    }['name' | 'children'];
}['name' | 'children'] | {
    readonly name: "Tree";
}['name'] | {
    readonly name: "Sun";
}['name']
//
type N = "Rock" | "Pebbles" | "Tree" | "Sun" // result type

游乐场链接


推荐阅读