打字稿:如何递归地从数组树中生成元组?
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 ]
.
到目前为止我做了什么:
- 将数据转换为常量:
data = [...] as const;
- 通用类型
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)]
我的解释是你有一个值 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 recursively 为 children
属性 评估 DataPaths
。
请注意,空元组无论如何都会合并到 DataElementPaths
的结果中(如 [] |
);正是这个允许部分路径;如果您删除了它,那么您将只能找到通往树叶的路径。
还要注意,为了使输入并集成为输出并集,这是作为 distributive conditional type 实现的;因此 T extends DataElement ? ... : never
。它看起来像一个 no-op(我们已经知道 T extends DataElement
)但是没有它你只会得到一个元组而不是联合。
并且您可以验证该实现确实为 ValidPaths
生成了所需的类型。
我有以下数据结构:
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 ]
.
到目前为止我做了什么:
- 将数据转换为常量:
data = [...] as const;
- 通用类型
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)]
我的解释是你有一个值 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 recursively 为 children
属性 评估 DataPaths
。
请注意,空元组无论如何都会合并到 DataElementPaths
的结果中(如 [] |
);正是这个允许部分路径;如果您删除了它,那么您将只能找到通往树叶的路径。
还要注意,为了使输入并集成为输出并集,这是作为 distributive conditional type 实现的;因此 T extends DataElement ? ... : never
。它看起来像一个 no-op(我们已经知道 T extends DataElement
)但是没有它你只会得到一个元组而不是联合。
并且您可以验证该实现确实为 ValidPaths
生成了所需的类型。