Github:github.com/huyikai/tre...
Tree-Conver 是一个能将扁平节点数组与树形数组相互转换的工具。
对外导出 arrayToTree
、treeToArray
两个方法。两个方法的基础情况下时间和空间复杂度均为 O(n),测试10w内数据的表现良好
treeToArray
方法增加了参数 addFields
、ignoreFields
,可以灵活的增减处理后数据中的字段。
安装使用
shell
npm i tree-conver
javascript
import { treeToArray, arrayToTree } from 'tree-conver';
数组转树
将一个扁平的节点数组转换成树形结构。
参数
它接受两个参数:
-
Array: 扁平的节点数组
-
Options: 一个可选的参数对象,用于配置转换方法的具体行为
参数 描述 类型 默认值 childrenKey 自定义节点 children 字段名称 string 'children' idKey 自定义节点 ID 字段名称 string 'id' pidKey 自定义节点父 ID 字段名称 string 'pid'
示例
javascript
const flatArray = [
{ uid: '1', name: 'node1', pid: null },
{ uid: '2', name: 'node2', pid: '1' },
{ uid: '3', name: 'node3', pid: '1' },
{ uid: '4', name: 'node4', pid: '2' },
{ uid: '5', name: 'node5', pid: '2' },
{ uid: '6', name: 'node6', pid: '3' }
];
const options = {
idKey: 'id',
pidKey: 'pid',
childrenKey: 'children'
};
const treeArray = arrayToTree(flatArray, options);
实现代码
typescript
interface Node {
id: string;
children?: Array<Node>;
pid?: string;
}
interface Options {
idKey?: string;
pidKey?: string;
childrenKey?: string;
}
export const arrayToTree = (
array: Array<Node | undefined>,
options: Options = {}
) => {
if (!Array.isArray(array)) {
throw new Error('The first argument must be an array.');
}
const { idKey = 'id', pidKey = 'pid', childrenKey = 'children' } = options;
const map = array.reduce((acc: Record<string, Node>, node: any) => {
acc[node[idKey]] = { ...node, [childrenKey]: [] };
return acc;
}, {});
Object.values(map).forEach((node: any) => {
const parentId = node[pidKey];
if (parentId) {
const parent: any = map[parentId];
if (!parent[childrenKey]) {
parent[childrenKey] = [];
}
parent[childrenKey].push(node);
}
});
const tree = Object.values(map).filter((node: any) => !node[pidKey]);
return tree;
};
树转数组
将树形结构的数据转换为扁平的数组。
参数
接受两个参数:
-
Tree: 树形结构数组
-
Options: 一个可选的参数对象,用于配置转换方法的具体行为
属性 描述 类型 默认值 addFields 需要添加的字段名称及其对应的属性值计算方法的列表 [{ fieldName: string;callback: (item) => any }] [] childrenKey 子节点的键名 string 'children' ignoreFields 要忽略的字段名称列表 string[] [] needParentId 是否添加节点信息的父节点 ID boolean true
示例
javascript
const treeArray = [
{
id: '1',
name: 'Node 1',
list: [
{
id: '2',
name: 'Node 2',
list: [
{
id: '3',
name: 'Node 3'
}
]
},
{
id: '4',
name: 'Node 4'
}
]
}
];
const calculateDepth = (node) => {
let depth = 0;
let parent = node;
while (parent) {
depth++;
parent =
parent['parentId'] && treeArray.find((n) => n.id === parent['parentId']);
}
return depth;
};
const options = {
childrenKey: 'list',
ignoreFields: [],
addFields: [
{
fieldName: 'hasChildren', // Add a new 'field' property with a boolean value
callback: (node) => Boolean(node['children'])
},
{
fieldName: 'depth', // Add a new 'depth' property with the depth of each node
callback: calculateDepth
}
],
needParentId: true
};
const flatArray = treeToArray(treeArray, options);
console.log(flatArray);
[
{
"id": "1",
"name": "Node 1",
"parentId": "",
"hasChildren": false,
"depth": 1
},
{
"id": "2",
"name": "Node 2",
"parentId": "1",
"hasChildren": false,
"depth": 1
},
{
"id": "3",
"name": "Node 3",
"parentId": "2",
"hasChildren": false,
"depth": 1
},
{
"id": "4",
"name": "Node 4",
"parentId": "1",
"hasChildren": false,
"depth": 1
}
]
实现代码
typescript
interface Node {
[key: string]: any;
children?: Array<Node>;
}
interface TreeToArrayOptions {
childrenKey?: string;
ignoreFields?: Array<string>;
addFields?: Array<{ fieldName: string; callback: (item: Node) => any }>;
needParentId?: boolean;
}
export const treeToArray = (
tree: Array<Node>,
options: TreeToArrayOptions = {}
): Array<Node> => {
const {
childrenKey = 'children',
ignoreFields = []
addFields = [],
needParentId = true
} = options;
const nodes: Array<Node> = [];
const stack: Array<{
node: Node | null;
children: Array<Node>;
parentId: string | null;
}> = [];
stack.push({
node: null,
children: tree,
parentId: null
});
while (stack.length) {
const { node, children, parentId } = stack.pop()!;
if (node) {
const { [childrenKey]: subChildren, ...rest } = node;
const newNode = { ...rest };
if (needParentId) {
newNode['parentId'] = parentId;
}
if (addFields.length) {
for (let i = 0; i < addFields.length; i++) {
newNode[addFields[i].fieldName] = addFields[i].callback(node);
}
}
if (ignoreFields.length) {
for (let i = 0; i < ignoreFields.length; i++) {
delete newNode[ignoreFields[i]];
}
}
nodes.push(newNode);
}
if (children) {
for (let i = children.length - 1; i >= 0; i--) {
stack.push({
node: children[i],
children: children[i][childrenKey] || [],
parentId: node?.id || ''
});
}
}
}
return nodes;
};