Skip to content

Commit edd2550

Browse files
committed
feat: add high performance utils for tree data
1 parent a5a39f9 commit edd2550

File tree

5 files changed

+459
-334
lines changed

5 files changed

+459
-334
lines changed

src/array.ts

Lines changed: 0 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -112,105 +112,6 @@ export function arrayRemove<V>(array: V[], expect: (val: V, idx: number) => bool
112112
return array;
113113
}
114114

115-
/**
116-
* 自定义深度优先遍历函数(支持continue和break操作)
117-
* @param {ArrayLike<V>} tree 树形数据
118-
* @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
119-
* @param {string} children 定制子元素的key
120-
* @param {boolean} isReverse 是否反向遍历
121-
* @returns {*}
122-
*/
123-
export function forEachDeep<V>(
124-
tree: ArrayLike<V>,
125-
iterator: (val: V, i: number, arr: ArrayLike<V>, parent: V | null, level: number) => boolean | void,
126-
children: string = 'children',
127-
isReverse = false
128-
) {
129-
let level = 0,
130-
isBreak = false;
131-
const walk = (arr: ArrayLike<V>, parent: V | null) => {
132-
if (isReverse) {
133-
for (let i = arr.length - 1; i >= 0; i--) {
134-
if (isBreak) {
135-
break;
136-
}
137-
const re = iterator(arr[i], i, tree, parent, level);
138-
if (re === false) {
139-
isBreak = true;
140-
break;
141-
} else if (re === true) {
142-
continue;
143-
}
144-
// @ts-ignore
145-
if (Array.isArray(arr[i][children])) {
146-
++level;
147-
// @ts-ignore
148-
walk(arr[i][children], arr[i]);
149-
}
150-
}
151-
} else {
152-
for (let i = 0; i < arr.length; i++) {
153-
if (isBreak) {
154-
break;
155-
}
156-
const re = iterator(arr[i], i, tree, parent, level);
157-
if (re === false) {
158-
isBreak = true;
159-
break;
160-
} else if (re === true) {
161-
continue;
162-
}
163-
// @ts-ignore
164-
if (Array.isArray(arr[i][children])) {
165-
++level;
166-
// @ts-ignore
167-
walk(arr[i][children], arr[i]);
168-
}
169-
}
170-
}
171-
};
172-
walk(tree, null);
173-
}
174-
export type IdLike = number | string;
175-
export interface ITreeConf {
176-
id: string | number;
177-
children: string;
178-
}
179-
/**
180-
* 在树中找到 id 为某个值的节点,并返回上游的所有父级节点
181-
*
182-
* @param {ArrayLike<T>} tree - 树形数据
183-
* @param {IdLike} nodeId - 元素ID
184-
* @param {ITreeConf} config - 迭代配置项
185-
* @returns {[IdLike[], ITreeItem<V>[]]} - 由parentId...childId, parentObject-childObject组成的二维数组
186-
*/
187-
export function searchTreeById<V>(tree: ArrayLike<V>, nodeId: IdLike, config?: ITreeConf): [IdLike[], ArrayLike<V>[]] {
188-
const { children = 'children', id = 'id' } = config || {};
189-
const toFlatArray = (tree, parentId?: IdLike, parent?: any) => {
190-
return tree.reduce((t, _) => {
191-
const child = _[children];
192-
return [
193-
...t,
194-
parentId ? { ..._, parentId, parent } : _,
195-
...(child && child.length ? toFlatArray(child, _[id], _) : [])
196-
];
197-
}, []);
198-
};
199-
const getIds = (flatArray): [IdLike[], ArrayLike<V>[]] => {
200-
let child = flatArray.find(_ => _[id] === nodeId);
201-
const { parent, parentId, ...other } = child;
202-
let ids = [nodeId],
203-
nodes = [other];
204-
while (child && child.parentId) {
205-
ids = [child.parentId, ...ids];
206-
nodes = [child.parent, ...nodes];
207-
child = flatArray.find(_ => _[id] === child.parentId); // eslint-disable-line
208-
}
209-
return [ids, nodes];
210-
};
211-
return getIds(toFlatArray(tree));
212-
}
213-
214115
/**
215116
* 异步ForEach函数
216117
* @param {array} array
@@ -239,104 +140,3 @@ async function asyncForEach(array: any[], callback: Function) {
239140
await callback(array[index], index, array);
240141
}
241142
}
242-
243-
/**
244-
* 使用迭代函数转换数组
245-
* @param {T} array
246-
* @param {Function} callback 迭代函数
247-
* @return {Array}
248-
*/
249-
function flatMap<T, U>(array: T[], callback: (value: T, index: number, array: T[]) => U[]): U[] {
250-
const result: U[] = [];
251-
252-
array.forEach((value, index) => {
253-
result.push(...callback(value, index, array));
254-
});
255-
256-
return result;
257-
}
258-
259-
export type WithChildren<T> = T & { children?: WithChildren<T>[] };
260-
261-
/**
262-
* 根据 idProp 与 parentIdProp 从对象数组中构建对应的树
263-
* 当 A[parentIdProp] === B[idProp] 时,对象A会被移动到对象B的children。
264-
* 当一个对象的 parentIdProp 不与其他对象的 idProp 字段相等时,该对象被作为树的顶层节点
265-
* @param {string} idProp 元素ID
266-
* @param {string} parentIdProp 父元素ID
267-
* @param {object[]} items 一维数组
268-
* @returns {WithChildren<T>[]} 树
269-
* @example
270-
* const array = [
271-
* { id: 'node-1', parent: 'root' },
272-
* { id: 'node-2', parent: 'root' },
273-
* { id: 'node-3', parent: 'node-2' },
274-
* { id: 'node-4', parent: 'node-2' },
275-
* { id: 'node-5', parent: 'node-4' },
276-
* ]
277-
* const tree = buildTree('id', 'parent', array)
278-
* expect(tree).toEqual([
279-
* { id: 'node-1', parent: 'root' },
280-
* {
281-
* id: 'node-2',
282-
* parent: 'root',
283-
* children: [
284-
* { id: 'node-3', parent: 'node-2' },
285-
* {
286-
* id: 'node-4',
287-
* parent: 'node-2',
288-
* children: [{ id: 'node-5', parent: 'node-4' }],
289-
* },
290-
* ],
291-
* },
292-
* ])
293-
*/
294-
export function buildTree<ID extends string, PID extends string, T extends { [key in ID | PID]: string }>(
295-
idProp: ID,
296-
parentIdProp: PID,
297-
items: T[]
298-
): WithChildren<T>[] {
299-
type Wrapper = { id: string; children: Wrapper[]; item: T; parent: Wrapper };
300-
301-
const wrapperMap = new Map<string, Wrapper>();
302-
const ensure = (id: string) => {
303-
if (wrapperMap.has(id)) {
304-
return wrapperMap.get(id);
305-
}
306-
//@ts-ignore
307-
const wrapper: Wrapper = { id, parent: null, item: null, children: [] };
308-
wrapperMap.set(id, wrapper);
309-
return wrapper;
310-
};
311-
for (const item of items) {
312-
const parentWrapper = ensure(item[parentIdProp]);
313-
const itemWrapper = ensure(item[idProp]);
314-
//@ts-ignore
315-
itemWrapper.parent = parentWrapper;
316-
//@ts-ignore
317-
parentWrapper.children.push(itemWrapper);
318-
//@ts-ignore
319-
itemWrapper.item = item;
320-
}
321-
const topLevelWrappers = flatMap(
322-
Array.from(wrapperMap.values()).filter(wrapper => wrapper.parent === null),
323-
wrapper => wrapper.children
324-
);
325-
326-
return unwrapRecursively(topLevelWrappers);
327-
328-
function unwrapRecursively(wrapperArray: Wrapper[]) {
329-
const result: WithChildren<T>[] = [];
330-
for (const wrapper of wrapperArray) {
331-
if (wrapper.children.length === 0) {
332-
result.push(wrapper.item);
333-
} else {
334-
result.push({
335-
...wrapper.item,
336-
children: unwrapRecursively(wrapper.children)
337-
});
338-
}
339-
}
340-
return result;
341-
}
342-
}

src/object.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ export function objectGet(
261261
* @param {WeakMap} map
262262
* @returns {AnyObject | AnyArray}
263263
*/
264-
export function cloneDeep(obj: Object, map = new WeakMap()): Object {
264+
export function cloneDeep(obj: Object, map = new WeakMap()): AnyObject | AnyArray {
265265
if (obj instanceof Date) return new Date(obj);
266266
if (obj instanceof RegExp) return new RegExp(obj);
267267

0 commit comments

Comments
 (0)