树形数据相互转换
把树形数据转换成扁平数组
查看代码
ts
// 节点类型 Node Type
interface Node {
[key: string]: any;
children?: Array<Node>;
}
// 入参类型
interface TreeToArrayOptions {
// 子节点的键名称,默认为'children'
childrenKey?: string;
// 需要忽略的字段名列表,默认为空列表
ignoreFields?: Array<string>;
// 需要添加的字段名及其对应属性值的计算方式列表,默认为空列表
addFields?: Array<{ fieldName: string; callback: (item: Node) => any }>;
// 子节点是否需要父节点的id,默认为true
needParentId?: boolean;
}
// 根据树状结构以数组形式返回所有节点(包括子孙节点)
export const treeToArray = (
tree: Array<Node>,
options: TreeToArrayOptions = {}
): Array<Node> => {
const {
// 子节点的键名称
childrenKey = 'children',
// 要忽略的字段名列表
ignoreFields = [],
// 需要添加的字段名及其对应属性值的计算方式列表
addFields = [],
// 子节点是否需要父节点的id,默认为true
needParentId = true
} = options;
const nodes: Array<Node> = [];
// stack用于迭代树结构中的子节点和子孙子节点
const stack: Array<{
// 正在处理的节点,包括该节点的信息、父节点的id、以及该节点的所有子节点
node: Node | null;
children: Array<Node>;
parentId: string | null;
}> = [];
// 将整个树的所有节点压入栈,其中root节点的parentId为空
stack.push({
node: null,
children: tree,
parentId: null
});
while (stack.length) {
const { node, children, parentId } = stack.pop()!;
if (node) {
// 存储该节点的所有属性到newNode中,除去childrenKey所指定的子节点信息
const { [childrenKey]: subChildren, ...rest } = node;
const newNode = { ...rest };
if (needParentId) {
// 如果needParentId为true,则将父节点的id添加到该节点的属性中
newNode['parentId'] = parentId;
}
if (addFields.length) {
// 如果需要添加属性值,则遍历addFields列表,计算对应的属性值
for (let i = 0; i < addFields.length; i++) {
newNode[addFields[i].fieldName] = addFields[i].callback(node);
}
}
if (ignoreFields.length) {
// 如果需要忽略某些属性,则遍历ignoreFields列表,将对应的属性从newNode中删除
for (let i = 0; i < ignoreFields.length; i++) {
delete newNode[ignoreFields[i]];
}
}
// 将newNode存入nodes数组中
nodes.push(newNode);
}
if (children) {
// 将该节点的所有子节点压入栈中,继续循环直到stack为空
for (let i = children.length - 1; i >= 0; i--) {
stack.push({
node: children[i],
children: children[i][childrenKey] || [],
parentId: node?.id || ''
});
}
}
}
// 返回以数组形式储存的所有节点(包括子孙节点)
return nodes;
};
参数
接受两个参数:
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', // 添加新字段 'hasChildren',用于判断是否有子节点
callback: (node) => Boolean(node['children'])
},
{
fieldName: 'depth', // 添加新字段 'depth',用于记录节点深度
callback: calculateDepth
}
],
needParentId: true
}
const flatArray = treeToArray(treeArray, options)
console.log(flatArray)
结果如下:
json
[
{ "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 }
]
把扁平数组转换成树形数据
查看代码
ts
interface Node {
id: string; // 节点的唯一标识符
children?: Array<Node>; // 节点的子节点数组
pid?: string; // 节点的父节点标识符
}
interface Options {
idKey?: string; // 自定义 id 字段的名称
pidKey?: string; // 自定义 pid 字段的名称
childrenKey?: string; // 自定义 children 字段的名称
}
export const arrayToTree = (
array: Array<Node | undefined>,
options: Options = {}
) => {
if (!Array.isArray(array)) {
throw new Error('The first argument must be an array.');
}
// 解构 options 对象来获取自定义字段名称或默认值
const { idKey = 'id', pidKey = 'pid', childrenKey = 'children' } = options;
// 创建节点 id 到节点的映射
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]) {
// 如果父节点没有 children 属性
parent[childrenKey] = []; // 初始化 children 属性
}
parent[childrenKey].push(node); // 将当前节点添加到父节点的 children 数组中
}
});
// 找到所有根节点(没有父节点的节点)并构建树
const tree = Object.values(map).filter((node: any) => !node[pidKey]);
return tree;
};
参数
它接受两个参数:
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)
生成随机树形数据
ts
interface Item {
// Add any existing properties
children?: Item[];
// Define the new property
id: string;
}
export const generateTree = (depth: number, width: number): Item => {
const node: Item = {
id: Math.floor(Math.random() * 1000000).toString()
};
if (depth > 0) {
node.children = [];
for (let i = 0; i < width; i++) {
node.children.push(generateTree(depth - 1, width));
}
}
return node;
};