Skip to content

Commit dc1f65a

Browse files
committed
feat: add forEachMap utils func
1 parent 629a4ae commit dc1f65a

File tree

4 files changed

+191
-7
lines changed

4 files changed

+191
-7
lines changed

docs/gen-api-docs.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ npm install -g @microsoft/api-extractor @microsoft/api-documenter
2020

2121
1. Init api-extractor
2222

23-
enter root directory of project
23+
enter into root directory of project
2424

2525
```bash
2626
api-extractor init
2727
```
2828

2929
after exec completely, will generate `api-extractor.json` file
3030

31-
2. generate API JSON file for api markdown
31+
2. Generate API JSON file for api markdown
3232

3333
```bash
3434
api-extractor run --local --verbose

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@
3737
"lib"
3838
],
3939
"keywords": [
40-
"typescript",
41-
"js-utils"
40+
"sculp-js",
41+
"js-utils",
42+
"typescript"
4243
],
4344
"engines": {
4445
"node": ">=16"

src/tree.ts

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface IFieldOptions {
66
const defaultFieldOptions = { keyField: 'key', childField: 'children', pidField: 'pid' };
77

88
/**
9-
* 自定义深度优先遍历函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
9+
* 深度优先遍历函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
1010
* @param {ArrayLike<V>} tree 树形数据
1111
* @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
1212
* @param {string} children 定制子元素的key
@@ -25,7 +25,7 @@ export function forEachDeep<V>(
2525
) => boolean | void,
2626
children: string = 'children',
2727
isReverse = false
28-
) {
28+
): void {
2929
let level = 0,
3030
isBreak = false;
3131
const walk = (arr: ArrayLike<V>, parent: V | null) => {
@@ -71,6 +71,85 @@ export function forEachDeep<V>(
7171
};
7272
walk(tree, null);
7373
}
74+
75+
/**
76+
* 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
77+
* @param {ArrayLike<V>} tree 树形数据
78+
* @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
79+
* @param {string} children 定制子元素的key
80+
* @param {boolean} isReverse 是否反向遍历
81+
* @returns {any[]} 新的一棵树
82+
*/
83+
export function forEachMap<V>(
84+
tree: ArrayLike<V>,
85+
iterator: (
86+
val: V,
87+
i: number,
88+
currentArr: ArrayLike<V>,
89+
tree: ArrayLike<V>,
90+
parent: V | null,
91+
level: number
92+
) => boolean | any,
93+
children: string = 'children',
94+
isReverse = false
95+
): any[] {
96+
let level = 0,
97+
isBreak = false;
98+
const newTree = [];
99+
const walk = (arr: ArrayLike<V>, parent: V | null, newTree: any[]) => {
100+
if (isReverse) {
101+
for (let i = arr.length - 1; i >= 0; i--) {
102+
if (isBreak) {
103+
break;
104+
}
105+
const re = iterator(arr[i], i, arr, tree, parent, level);
106+
if (re === false) {
107+
isBreak = true;
108+
break;
109+
} else if (re === true) {
110+
continue;
111+
}
112+
newTree.push(re);
113+
// @ts-ignore
114+
if (arr[i] && Array.isArray(arr[i][children])) {
115+
++level;
116+
newTree[newTree.length - 1][children] = [];
117+
// @ts-ignore
118+
walk(arr[i][children], arr[i], newTree[newTree.length - 1][children]);
119+
} else {
120+
// children非有效数组时,移除该属性字段
121+
delete re[children];
122+
}
123+
}
124+
} else {
125+
for (let i = 0; i < arr.length; i++) {
126+
if (isBreak) {
127+
break;
128+
}
129+
const re = iterator(arr[i], i, arr, tree, parent, level);
130+
if (re === false) {
131+
isBreak = true;
132+
break;
133+
} else if (re === true) {
134+
continue;
135+
}
136+
newTree.push(re);
137+
// @ts-ignore
138+
if (arr[i] && Array.isArray(arr[i][children])) {
139+
++level;
140+
newTree[newTree.length - 1][children] = [];
141+
// @ts-ignore
142+
walk(arr[i][children], arr[i], newTree[newTree.length - 1][children]);
143+
} else {
144+
// children非有效数组时,移除该属性字段
145+
delete re[children];
146+
}
147+
}
148+
}
149+
};
150+
walk(tree, null, newTree);
151+
return newTree;
152+
}
74153
export type IdLike = number | string;
75154
export interface ITreeConf {
76155
id: string | number;

test/tree.test.ts

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { cloneDeep } from '../src/object';
2-
import { formatTree, searchTreeById, forEachDeep, buildTree } from '../src/tree';
2+
import { formatTree, searchTreeById, forEachDeep, buildTree, forEachMap } from '../src/tree';
33

44
test('searchTreeById', () => {
55
const tree = [
@@ -217,3 +217,107 @@ test('forEachDeep: insert tree item', () => {
217217
{ id: 3, name: 'row3' }
218218
]);
219219
});
220+
221+
test('forEachMap', () => {
222+
const tree = [
223+
{ id: 1, name: 'row1', age: 1 },
224+
{
225+
id: 2,
226+
name: 'row2',
227+
age: 2,
228+
children: [{ id: 21, name: 'row2-1', age: 21 }]
229+
},
230+
{ id: 3, name: 'row3' }
231+
];
232+
233+
let res1: any[] = [];
234+
let res2: any[] = [];
235+
let res3: any[] = [];
236+
let res4: any[] = [];
237+
let res5: any[] = [];
238+
239+
res1 = forEachMap(tree, ({ id, name, children }) => {
240+
return { key: id, label: name, children };
241+
});
242+
expect(res1).toEqual([
243+
{ key: 1, label: 'row1' },
244+
{
245+
key: 2,
246+
label: 'row2',
247+
children: [{ key: 21, label: 'row2-1' }]
248+
},
249+
{ key: 3, label: 'row3' }
250+
]);
251+
252+
// reverse traversal
253+
res2 = forEachMap(
254+
tree,
255+
({ id, name, children }) => {
256+
return { key: id, label: name, children };
257+
},
258+
'children',
259+
true
260+
);
261+
262+
expect(res2).toEqual([
263+
{ key: 3, label: 'row3' },
264+
{
265+
key: 2,
266+
label: 'row2',
267+
children: [{ key: 21, label: 'row2-1' }]
268+
},
269+
{ key: 1, label: 'row1' }
270+
]);
271+
// test continue
272+
res3 = forEachMap(tree, ({ id, name, children }) => {
273+
if (id === 21) {
274+
return true;
275+
}
276+
return { key: id, label: name, children, job: `job-${id}` };
277+
});
278+
expect(res3).toEqual([
279+
{ key: 1, label: 'row1', job: 'job-1' },
280+
{
281+
key: 2,
282+
label: 'row2',
283+
job: 'job-2',
284+
children: []
285+
},
286+
{ key: 3, label: 'row3', job: 'job-3' }
287+
]);
288+
289+
// test break
290+
res4 = forEachMap(tree, ({ id, name, children }) => {
291+
if (id === 21) {
292+
return false;
293+
}
294+
return { key: id, label: name, children };
295+
});
296+
expect(res4).toEqual([
297+
{ key: 1, label: 'row1' },
298+
{
299+
key: 2,
300+
label: 'row2',
301+
children: []
302+
}
303+
]);
304+
// test insert tree item
305+
res5 = forEachMap(tree, ({ id, name, children }, i, currentArr: any) => {
306+
if (id === 21) {
307+
currentArr.push({ id: 22, name: 'row2-2' });
308+
}
309+
return { key: id, label: name, children };
310+
});
311+
expect(res5).toEqual([
312+
{ key: 1, label: 'row1' },
313+
{
314+
key: 2,
315+
label: 'row2',
316+
children: [
317+
{ key: 21, label: 'row2-1' },
318+
{ key: 22, label: 'row2-2' }
319+
]
320+
},
321+
{ key: 3, label: 'row3' }
322+
]);
323+
});

0 commit comments

Comments
 (0)