开发了一个树结构数组、扁平化数组相互转换的工具

Github:github.com/huyikai/tre...

Tree-Conver 是一个能将扁平节点数组与树形数组相互转换的工具。

对外导出 arrayToTreetreeToArray 两个方法。两个方法的基础情况下时间和空间复杂度均为 O(n),测试10w内数据的表现良好

treeToArray 方法增加了参数 addFieldsignoreFields,可以灵活的增减处理后数据中的字段。

安装使用

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;
};
相关推荐
-To be number.wan19 分钟前
C++ 赋值运算符重载:深拷贝 vs 浅拷贝的生死线!
前端·c++
YGGP27 分钟前
【Golang】LeetCode 64. 最小路径和
算法·leetcode
噢,我明白了35 分钟前
JavaScript 中处理时间格式的核心方式
前端·javascript
纸上的彩虹1 小时前
半年一百个页面,重构系统也重构了我对前端工作的理解
前端·程序员·架构
古城小栈2 小时前
Rust变量设计核心:默认不可变与mut显式可变的深层逻辑
算法·rust
be or not to be2 小时前
深入理解 CSS 浮动布局(float)
前端·css
电商API&Tina2 小时前
跨境电商 API 对接指南:亚马逊 + 速卖通接口调用全流程
大数据·服务器·数据库·python·算法·json·图搜索算法
LYFlied2 小时前
【每日算法】LeetCode 1143. 最长公共子序列
前端·算法·leetcode·职场和发展·动态规划
老华带你飞2 小时前
农产品销售管理|基于java + vue农产品销售管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
小徐_23333 小时前
2025 前端开源三年,npm 发包卡我半天
前端·npm·github