@utilslib/core/flattenTreeArray
打平嵌套的树形结构,并为每个节点添加 level 和 parentId 字段。
支持 Array、Map、Set、Object 等多种集合类型
flattenTreeArray
函数签名
typescript
function flattenTreeArray<T extends {
level?: never;
parentId?: never;
[key: string]: any;
}, P extends keyof T, ID extends keyof T, R = T & { level: number; parentId: T[ID] }>(collection: T[] | Map<any, T> | Set<T> | Record<string, T>, childrenProperty: P, idAttr: ID, includeParent: boolean = true): R[]描述
打平嵌套的树形结构,并为每个节点添加 level 和 parentId 字段。 支持 Array、Map、Set、Object 等多种集合类型
类型参数
| 参数名 | 约束 | 默认值 | 描述 |
|---|---|---|---|
T | \{ level?: never; parentId?: never; \[key: string\]: any; \} | - | - 树节点类型 |
P | keyof T | - | - 子节点属性的键名类型 |
ID | keyof T | - | - 节点 ID 属性的键名类型 |
R | - | T & \{ level: number; parentId: T\[ID\] \} | - 返回类型 |
参数
| 参数名 | 类型 | 可选 | 默认值 | 描述 |
|---|---|---|---|---|
collection | T[] | Map<any, T> | Set<T> | Record<string, T> | 否 | - | - |
childrenProperty | P | 否 | - | - |
idAttr | ID | 否 | - | - |
includeParent | boolean | 是 | true | - |
返回值
R\[\]
点击查看源码
js
/**
* 检查集合是否非空
*/
function isNonEmptyCollection(data) {
if (!data) return false;
if (Array.isArray(data)) return data.length > 0;
if (data instanceof Map || data instanceof Set) return data.size > 0;
if (typeof data === "object") return Object.keys(data).length > 0;
return false;
}
/**
* 通用的条目迭代器
* - Map: [key, value]
* - Set: [value, value]
* - Array/Iterable: [index, value]
* - Object: [key, value]
*/
function* entries(data) {
if (data instanceof Map) yield* data;
else if (data instanceof Set) {
for (const v of data) yield [v, v];
} else if (typeof data[Symbol.iterator] === "function") {
let i = 0;
for (const v of data) yield [i++, v];
} else if (data) {
yield* Object.entries(data);
}
}
/**
* 使用深度优先搜索(DFS)遍历树形结构,并为每个节点调用回调函数
* 支持遍历 Array、Map、Set、Object 等多种集合类型
*
* @template T - 树节点类型
* @param {T | T[] | Map | Set | Object | null | undefined} o - 要遍历的树或节点集合
* @param {keyof T} childrenKey - 子节点属性的键名
* @param {WalkTreeCallback<T>} cb - 遍历时的回调函数
*/
function walkTree(o, childrenKey, cb) {
if (!o) return;
// 保持根节点的原始集合类型
const rootNodes = Array.isArray(o)
? o
: o instanceof Map || o instanceof Set || typeof o === "object"
? o
: [o];
let breakLoop = false;
// DFS递归遍历函数
function dfsTraverse(nodes, level, ancestors = []) {
if (!isNonEmptyCollection(nodes)) return false;
if (breakLoop) return true;
// 使用 entries 遍历,支持多种集合类型
for (const [index, node] of entries(nodes)) {
const typedNode = node;
const children = typedNode[childrenKey];
const leaf = !isNonEmptyCollection(children);
// 调用回调函数
cb(
typedNode,
{
level,
index: index,
leaf,
peerList: nodes,
parent: ancestors.slice(-1)[0]?.data,
ancestors: [...ancestors],
},
() => {
breakLoop = true;
},
);
if (breakLoop) return true;
if (!leaf) {
const newAncestors = [...ancestors, { index: index, data: typedNode }];
if (dfsTraverse(children, level + 1, newAncestors)) return true;
}
}
return false;
}
dfsTraverse(rootNodes, 0);
}
/**
* 打平嵌套的树形结构,并为每个节点添加 level 和 parentId 字段。
* 支持 Array、Map、Set、Object 等多种集合类型
*
* @template T - 树节点类型
* @template P - 子节点属性的键名类型
* @template ID - 节点 ID 属性的键名类型
* @template R - 返回类型
* @param {T[] | Map | Set | Object} collection - 嵌套的树形结构集合
* @param {P} childrenProperty - 子节点属性的键名
* @param {ID} idAttr - 节点 ID 属性的键名
* @param {boolean} [includeParent=true] - 是否包含父节点,默认为 true
* @returns {R[]} 打平后的数组
*/
export function flattenTreeArray(
collection,
childrenProperty,
idAttr,
includeParent = true,
) {
const result = [];
walkTree(collection, childrenProperty, (node, context) => {
const { level, leaf, parent } = context;
const data = node;
if (leaf || includeParent) {
result.push(
Object.assign({}, data, { level, parentId: parent?.[idAttr] }),
);
}
});
return result;
}ts
/**
* 遍历树的回调函数类型
* @template T - 树节点类型
* @template C - 子节点集合类型
*/
type WalkTreeCallback<T, C = any> = (
item: T,
context: {
/** 当前节点的层级 */
level: number;
/** 当前节点在同级节点中的索引(数组为number,Map/Object为key,Set为值本身) */
index: number | string | T;
/** 当前节点是否为叶子节点 */
leaf: boolean;
/** 当前节点的同级节点列表(保持原始集合类型) */
peerList: C;
/** 当前节点的父节点信息 */
parent?: T;
/** 当前节点的所有祖先节点列表 */
ancestors: ParentInfo<T>[];
},
// 停止遍历
breakLoop: () => void,
) => void;
/**
* 父节点信息接口
* @template T - 树节点类型
*/
export interface ParentInfo<T> {
/** 在同级节点中的索引(数组为number,Map/Object为key,Set为值本身) */
index: number | string | T;
/** 父节点数据 */
data: T;
}
/**
* 检查集合是否非空
*/
function isNonEmptyCollection(data: any): boolean {
if (!data) return false;
if (Array.isArray(data)) return data.length > 0;
if (data instanceof Map || data instanceof Set) return data.size > 0;
if (typeof data === "object") return Object.keys(data).length > 0;
return false;
}
/**
* 通用的条目迭代器
* - Map: [key, value]
* - Set: [value, value]
* - Array/Iterable: [index, value]
* - Object: [key, value]
*/
function* entries<T, K, V>(
data: Map<K, V> | Set<T> | Iterable<T> | Record<string, any>,
): Generator<[K | number | string | T, V | T], void, unknown> {
if (data instanceof Map) yield* data;
else if (data instanceof Set) {
for (const v of data) yield [v, v] as [T, T];
} else if (typeof (data as Iterable<T>)[Symbol.iterator] === "function") {
let i = 0;
for (const v of data as Iterable<T>) yield [i++, v] as [number, T];
} else if (data) {
yield* Object.entries(data) as [string, any][];
}
}
/**
* 使用深度优先搜索(DFS)遍历树形结构,并为每个节点调用回调函数
* 支持遍历 Array、Map、Set、Object 等多种集合类型
*
* @template T - 树节点类型
* @param {T | T[] | Map | Set | Object | null | undefined} o - 要遍历的树或节点集合
* @param {keyof T} childrenKey - 子节点属性的键名
* @param {WalkTreeCallback<T>} cb - 遍历时的回调函数
*/
function walkTree<T extends Record<string, any>, K extends keyof T>(
o: T | T[] | Map<any, T> | Set<T> | Record<string, T> | null | undefined,
childrenKey: K,
cb: WalkTreeCallback<T>,
): void {
if (!o) return;
// 保持根节点的原始集合类型
const rootNodes = Array.isArray(o)
? o
: o instanceof Map || o instanceof Set || typeof o === "object"
? o
: [o];
let breakLoop = false;
// DFS递归遍历函数
function dfsTraverse(
nodes: any,
level: number,
ancestors: ParentInfo<T>[] = [],
): boolean {
if (!isNonEmptyCollection(nodes)) return false;
if (breakLoop) return true;
// 使用 entries 遍历,支持多种集合类型
for (const [index, node] of entries(nodes)) {
const typedNode = node as T;
const children = typedNode[childrenKey];
const leaf = !isNonEmptyCollection(children);
// 调用回调函数
cb(
typedNode,
{
level,
index: index as number | string | T,
leaf,
peerList: nodes,
parent: ancestors.slice(-1)[0]?.data,
ancestors: [...ancestors],
},
() => {
breakLoop = true;
},
);
if (breakLoop) return true;
if (!leaf) {
const newAncestors = [
...ancestors,
{ index: index as number | string | T, data: typedNode },
];
if (dfsTraverse(children, level + 1, newAncestors)) return true;
}
}
return false;
}
dfsTraverse(rootNodes, 0);
}
/**
* 打平嵌套的树形结构,并为每个节点添加 level 和 parentId 字段。
* 支持 Array、Map、Set、Object 等多种集合类型
*
* @template T - 树节点类型
* @template P - 子节点属性的键名类型
* @template ID - 节点 ID 属性的键名类型
* @template R - 返回类型
* @param {T[] | Map | Set | Object} collection - 嵌套的树形结构集合
* @param {P} childrenProperty - 子节点属性的键名
* @param {ID} idAttr - 节点 ID 属性的键名
* @param {boolean} [includeParent=true] - 是否包含父节点,默认为 true
* @returns {R[]} 打平后的数组
*/
export function flattenTreeArray<
T extends {
level?: never;
parentId?: never;
[key: string]: any;
},
P extends keyof T,
ID extends keyof T,
R = T & { level: number; parentId: T[ID] },
>(
collection: T[] | Map<any, T> | Set<T> | Record<string, T>,
childrenProperty: P,
idAttr: ID,
includeParent: boolean = true,
): R[] {
const result: R[] = [];
walkTree(collection, childrenProperty, (node, context) => {
const { level, leaf, parent } = context;
const data = node;
if (leaf || includeParent) {
result.push(
Object.assign({}, data, { level, parentId: parent?.[idAttr] }) as R,
);
}
});
return result;
}如有错误,请提交issue :::