如何将扁平的数组数据转为树形结构

353 阅读2分钟

一、为什么要用数组转树

在我们渲染后台数据时并不是所有的数据都是平铺直叙的,最典型的就是在后台管理平台上,一定会有的员工和部门的上下级管理关系,这是时候我们就需要使用数组树形结构

二、完成数组转树形结构的方法

1. 性能不好,实现较为简单:递归方式

 
/**
 * 方法一:简单递归
 * @param { Array } data 数据源
 * @param { Array } result 输出结果
 * @param { Number | String } parentId 根id
 */
 
const getChildren = (data, result = [], parentId) => {
  for (const item of data) {
    if (item.parentId === parentId) {
      const newItem = { ...item, children: [] };
      result.push(newItem);
      getChildren(data, newItem.children, item.id);
    }
  }
  return result;
};
 
const res2 = getChildren(array, [], 0);
console.log("res2", res2);
/**
 * 方法二:递归实现
 * @param { Array } list 数组
 * @param { String } parentId 父级 id
 * @param { Object } param2 可配置参数
 */
 
const generateTree = (
  list,
  parentId = 0,
  { idName = "id", parentIdName = "parentId", childName = "children" } = {}
) => {
  if (!Array.isArray(list)) {
    throw new Error("type only Array");
    // new Error("type only Array");
    return list;
  }
  return list.reduce((pre, cur) => {
    // 找到parentId 的子节点之后,递归找子节点的下一级节点
    if (cur[parentIdName] === parentId) {
      const children = generateTree(list, cur[idName]);
      if (children.length) {
        cur[childName] = children;
      }
      return [...pre, cur];
    }
    return pre;
  }, []);
};
const result = generateTree(array, 0);

2. 性能可以,采用非递归方式

应用了对象保存的是引用的特点,每次将当前节点的 id 作为 key,保存对应节点的引用信息,遍历数组时,每次更新 objMap 的 children 信息,这样 objMap中保留了所有节点极其子节点,最重要的是,我们只需要遍历一遍数组

/**
 * 方法三:不用递归的简单循环
 * @param { Array } 源数据
 */
 
const arrayToTree = (items) => {
  const result = []; // 结果集
  const itemMap = {};
 
  // 先转成map存储
  for (const item of items) {
    itemMap[item.id] = { ...item, children: [] };
  }
 
  for (const item of items) {
    const id = item.id;
    const parentId = item.parentId;
    const treeItem = itemMap[id];
 
    if (parentId === 0) {
      result.push(treeItem);
    } else {
      if (!itemMap[parentId]) {
        itemMap[parentId] = { children: [] };
      }
      itemMap[parentId].children.push(treeItem);
    }
  }
  return result;
};
 
const res3 = arrayToTree(array);
console.log("res3", res3);
/**
 * 方法四:非递归实现 (映射 + 引用)
 * 前提:每一项都有parentId,根元素
 * @param { Array } list 数组
 * @param { String } rootId 根元素Id
 * @param { Object } param2 可配置参数
 */
 
const generateTree2 = (
  list,
  rootId = 0,
  { idName = "id", parentIdName = "parentId", childName = "childern" } = {}
) => {
  if (!Array.isArray(list)) {
    new Error("type only Array");
    return list;
  }
  const objMap = {}; //暂存数组以 id 为 key的映射关系
  const result = []; // 结果
 
  for (const item of list) {
    const id = item[idName];
    const parentId = item[parentIdName];
 
    // 该元素有可能已经放入map中,(找不到该项的parentId时 会先放入map
    objMap[id] = !objMap[id] ? item : { ...item, ...objMap[id] };
 
    const treeItem = objMap[id]; // 找到映射关系那一项(注意这里是引用)
 
    if (parentId === rootId) {
      // 已经到根元素则将映射结果放进结果集
      result.push(treeItem);
    } else {
      // 若父元素不存在,初始化父元素
      if (!objMap[parentId]) {
        objMap[parentId] = [];
      }
 
      // 若无该根元素则放入map中
      if (!objMap[parentId][childName]) {
        objMap[parentId][childName] = [];
      }
      objMap[parentId][childName].push(treeItem);
    }
  }
  return result;
};
 
const res = generateTree2(array);
console.log("res", res);

大佬原文地址1: # 面试了十几个高级前端,竟然连(扁平数据结构转Tree)都写不出来

大佬原文地址2: # 1w条数据,平铺数组转树形结构