Skip to content

Commit 41638e7

Browse files
committed
feat(kitify): Add listToTree utility for converting flat lists to trees
* Implements iterative list-to-tree conversion * Supports custom id, parent, and children keys * Allows node transformation via transform function * Includes root node detection logic * Exports as default function for reusability
1 parent ef3ebc5 commit 41638e7

File tree

1 file changed

+121
-0
lines changed

1 file changed

+121
-0
lines changed

libs/kitify/src/data/listToTree.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* 将列表数据转换为树形数据 (迭代方式)
3+
* 1. 首先创建一个 map 对象,用于通过 id 快速查找节点。
4+
* 2. 遍历列表,将每个节点添加到 map 中,并初始化 children 数组。
5+
* 3. 再次遍历列表,将每个节点添加到其父节点的 children 数组中。
6+
* 4. 最后返回根节点数组。
7+
*
8+
* 复杂度分析:
9+
* 1. 时间复杂度:O(n),其中 n 是列表的长度。
10+
* 2. 空间复杂度:O(n),其中 n 是列表的长度。
11+
*/
12+
13+
type ListItem = {
14+
[key: string]: any;
15+
};
16+
17+
// 配置项类型
18+
//! 需要注意 childrenKey 的类型推导
19+
type Options<T extends ListItem, R extends ListItem, K extends string = "children"> = {
20+
/**
21+
* 节点标识字段
22+
* @default "id"
23+
*/
24+
idKey?: string;
25+
26+
/**
27+
* 父节点标识字段
28+
* @default "parentId"
29+
*/
30+
parentKey?: string;
31+
32+
/**
33+
* 子节点字段
34+
* @default "children"
35+
*/
36+
childrenKey?: K;
37+
38+
/**
39+
* 转换节点函数, 默认copy当前节点
40+
* @param item 当前节点
41+
* @returns
42+
*/
43+
transform?: (item: T) => R;
44+
45+
/**
46+
* 判断是否为根节点
47+
* @param item 当前节点
48+
* @returns
49+
*/
50+
isRoot?: (item: T) => boolean;
51+
};
52+
53+
// 定义树节点类型,结合 transform 返回值和动态的 children 字段
54+
// 使用映射类型 [P in K] 来创建动态的 children 字段名
55+
type TreeNode<R extends ListItem, K extends string> = R & {
56+
[P in K]: TreeNode<R, K>[];
57+
};
58+
59+
/**
60+
* Converts a flat list of items into a hierarchical tree structure.
61+
* @param list List data
62+
* @param options Options
63+
* @returns tree data
64+
*/
65+
function listToTree<T extends ListItem, R extends ListItem = T, K extends string = "children">(
66+
list: T[],
67+
options: Options<T, R, K> = {} as Options<T, R, K>
68+
): TreeNode<R, K>[] {
69+
const {
70+
idKey = 'id',
71+
parentKey = 'parentId',
72+
childrenKey = 'children' as K, //默认值的类型断言
73+
transform = ((item: T) => ({ ...item } as unknown as R)), //! 注意需要类型断言
74+
isRoot = ((item: T) => !item[parentKey]),
75+
} = options;
76+
77+
if (!Array.isArray(list)) {
78+
throw new TypeError('Expected an array of items');
79+
}
80+
81+
if (!list.length) {
82+
return [];
83+
}
84+
85+
if (typeof transform !== 'function') {
86+
throw new TypeError('Expected transform to be a function');
87+
}
88+
89+
if (typeof isRoot !== 'function') {
90+
throw new TypeError('Expected isRoot to be a function');
91+
}
92+
93+
const map: Record<string | number, TreeNode<R, K>> = {};
94+
const roots: TreeNode<R, K>[] = [];
95+
96+
// 建立映射关系,方便通过 id 快速查找
97+
for (const node of list) {
98+
map[node[idKey]] = {
99+
...transform(node),
100+
[childrenKey]: [],
101+
} as TreeNode<R, K>;
102+
}
103+
104+
// 构建树形结构
105+
for (const node of list) {
106+
const parentId = node[parentKey];
107+
108+
if (isRoot(node)) {
109+
roots.push(map[node[idKey]]);
110+
} else {
111+
const parent = map[parentId];
112+
if (parent) {
113+
parent[childrenKey].push(map[node[idKey]]);
114+
}
115+
}
116+
}
117+
118+
return roots;
119+
}
120+
121+
export default listToTree;

0 commit comments

Comments
 (0)