打字稿:如何递归地从数组树中生成元组?

Typescript: how to generate Tuple out of Array Tree recursively?

我有以下数据结构:

const data = [{
   value: 'value',
   label: 'Label',
   children: [
    {
       value: 'value.1',
       label: 'Label.1',
       children: [{
         value: 'value.1.1',
         label: 'Label.1.1',
       }],
    }, 
    {
       value: 'value.2',
       label: 'Label.2',
       children: [{
         value: 'value.2.1',
         label: 'Label.2.1',
       }],
    }
   ],
},{
   value: 'value2',
   label: 'Label2',
}] as const;

我的目标是基于 data 生成以下元组(编辑器)自动完成:

[parent, children, childrenOfChildren]

与:

parent = 'value' | 'value2'; 
children = if parent === 'value' ? 'value.1' | 'value.2'; // The comment below explain the flow
// ['value' | 'value2'] > 'value' > ['value', 'value.1' | 'value.2'] > 'value.1' > ['value', 'value.1', 'value.1.1' | 'value.1.2'] > 'value.1.1' > ['value', 'value.1', 'value.1.1']
...

所以根据子数组深度,元组应该是动态data 中表示 key 的对象。 选择 'value' 将如下所示:

['value' | 'value2', 'value.1' | 'value.2' ] 其中 'value.1' | 'value.2' 可以选择。

选择 'value' 然后选择 'value.1' 将是:

['value', 'value.1', or (value.1).children.value ].

到目前为止我做了什么:

  1. 将数据转换为常量:data = [...] as const;
  2. 通用类型
declare const generateTuple: <T extends Record<K, PropertyKey | T[]>, K extends keyof T>(
  objArray: readonly T[],
  property: K,
) => [T[K]];

这成功生成了给定键的类型,但不是深度。我找不到递归生成 return 元组类型的方法:

[T[K], generateTuple(T[K]['children'], k)]

playground

我的解释是你有一个值 data 类型扩展 Data 定义如下:

type Data = readonly DataElement[];

interface DataElement {
  value: string;
  label: string;
  children?: Data;
}

并且您想要定义一个像 type DataPaths<T extends Data> 这样的类型函数,其计算结果为 union of tuple types 表示 value 属性在树中的每条可能路径。对于您问题中给出的 data,这应该类似于:

type ValidPaths = DataPaths<typeof data>
// type ValidPaths = [] | ["value"] | ["value", "value.1"] | 
// ["value", "value.1", "value.1.1"] | ["value", "value.2"] | 
// ["value", "value.2", "value.2.1"] | ["value2"]

我不清楚您是否真的只想要一直延伸到树结构的 leaf 节点的路径,或者您是否接受在叶子之前结束的路径。我选择后者,这就是为什么上面包含 ["value"] 以及空元组 [].


这是实现它的一种方法:

type DataPaths<T extends Data | undefined> = 
  [T] extends [Data] ? DataElementPaths<T[number]> : []

type DataElementPaths<T extends DataElement> = [] | (
  T extends DataElement ? [T["value"], ...DataPaths<T["children"]>] : never
)

想法是 DataPaths 作用于 DataElement 类型的数组(可能是 undefined),而 DataElement 作用于 DataElement 类型(或它们的联合)。 DataPaths<T> 类型实现为 return DataElementPaths 用于 T 的所有数组元素(如果它是数组)或空元组(如果它是 undefined)。

DataElementPaths 只是前置(通过 variadic tuple types) the value property of the DataElement to the result of recursivelychildren 属性 评估 DataPaths

请注意,空元组无论如何都会合并到 DataElementPaths 的结果中(如 [] | );正是这个允许部分路径;如果您删除了它,那么您将只能找到通往树叶的路径。

还要注意,为了使输入并集成为输出并集,这是作为 distributive conditional type 实现的;因此 T extends DataElement ? ... : never。它看起来像一个 no-op(我们已经知道 T extends DataElement)但是没有它你只会得到一个元组而不是联合。


并且您可以验证该实现确实为 ValidPaths 生成了所需的类型。

Playground link to code