JavaScript 平铺数据转tree格式数据

253 阅读5分钟

示例数据

// value属性为唯一标识的属性, parentId为关联的父节点
const data = [
  { label: '1', value: '1' },
  { label: '2-1', value: '2-1', parentId: '2' },
  { label: '1-1', value: '1-1', parentId: '1' },
  { label: '3', value: '3' },
  { label: '3-1-2-1', value: '3-1-2-1', parentId: '3-1-2' },
  { label: '3-1', value: '3-1', parentId: '3' },
  { label: '3-2', value: '3-2', parentId: '3' },
  { label: '2', value: '2' },
  { label: '3-1-1', value: '3-1-1', parentId: '3-1' },
  { label: '3-1-2', value: '3-1-2', parentId: '3-1' },
  { label: '3-1-1-1', value: '3-1-1-1', parentId: '3-1-1' },
  { label: '1-1-1', value: '1-1-1', parentId: '1-1'},
];

获取Tree数据的总节点的父节点的值

多数情况下Tree数据的总节点的父节点的值为空(null、undefined、0),但对于有权限控制的数据,不同用户看到不同的数据时,总节点的父节点的值可能不为空,所以有时候要先特殊处理获取到总结点的父节点的值。

  • 新建一个Map,第一次遍历数据以所有当前遍历数据的父节点的值作为key存储
  • 第二次遍历数据以所有当前遍历数据节点的值作为key进行删除
  • 最后对象剩余的一项的key是Tree总节点的父节点值
/*
* data: array 平铺格式的数据
* uniqueKey: string 数组中每一项唯一标识所对应的属性
* relativeKey: string 数组中每一项关联父节点所对应的属性
*/

const getFirstTreeKey = (data, uniqueKey, relativeKey) => {
  const idMap = new Map();
  data.forEach((item) => {
    idMap.set(item[relativeKey], true);
  });
  data.forEach((item) => {
    idMap.delete(item[uniqueKey]);
  });
  return Array.from(idMap.keys())[0];
}

找到Tree的总节点,从总节点开始,逐级获取所有子节点(时间复杂度较高 O(n^2))

  • 每一次查找当前节点的子节点都要从全量扁平化数据中根据当前节点的唯一值去过滤获取所有子节点。
  • 第一次调用时要先获取总节点的父节点的值当作第二个参数传递。
/*
* data: array 平铺格式的数据
* parentId: string | number 要查找的所有子节点关联的父节点值(根据传入的此值过滤获取所有子节点)
* uniqueKey: string 数组中每一项唯一标识所对应的属性
* relativeKey: string 数组中每一项关联父节点所对应的属性
*/

const generateTree = (data, parentId, uniqueKey, relativeKey) => {
  // 扁平化数据过滤获取匹配传入parentId的所有子节点
  return data.filter((item) => {
    if (item[relativeKey] === parentId) {
      // 遍历的数据是想要匹配节点的子节点时再获取当前遍历数据的所有子节点
      const childrenData = generateTree(data, item[uniqueKey], uniqueKey, relativeKey);
      if (childrenData.length) {
        item.children = childrenData;
      }
      return true;
    }
    return false;
  });
}

// 获取Tree数据的总节点的父节点的值
const firstTreeKey = getFirstTreeKey(data, 'value', 'parentId');

// 第一次调用时第二个参数要传总节点的父节点的值
const treeData = generateTree(data, firstTreeKey, 'value', 'parentId');

创建中间对象存储每一项的数据,然后获取每一项数据的子节点 (时间复杂度较低 O(2n))

  • 第一次遍历对象形式存储每一项数据(key为数据唯一标识,value是当前的每一项数据)
  • 第二次遍历如果当前数据有父节点则将数据插入到temptree对应父节点的数据中,如果当前数据无父节点则将temptree中对应当前数据key的值插入最终的数据中(temptree中的数据有子节点数据)
/*
* data: array 平铺格式的数据
* uniqueKey: string 数组中每一项唯一标识所对应的属性
* relativeKey: string 数组中每一项关联父节点所对应的属性
*/

const generateTree = (data, uniqueKey, relativeKey) => {
  const treeData = []; // 最后返回的tree结构数据
  const temptree = {}; // 对象形式存储每一项数据(key为数据唯一标识,value是当前的每一项数据)
  data.forEach(item => {
    if (!temptree[item[uniqueKey]]) {
      temptree[item[uniqueKey]] = item;
    }
  });
  data.forEach(item => {
    // 如果当前数据有父节点则将数据插入到temptree对应父节点的数据中
    if (temptree[item[relativeKey]]) {
      if (temptree[item[relativeKey]].children) {
        temptree[item[relativeKey]].children.push(temptree[item[uniqueKey]]);
      } else {
        temptree[item[relativeKey]].children = [temptree[item[uniqueKey]]];
      }
    } else {
      // 如果当前数据无父节点则直接插入最终的数据中
      treeData.push(temptree[item[uniqueKey]]);
    }
  });
  return treeData;
}

const treeData = generateTree(data, 'value', 'parentId');

传入Tree数据的总节点的父节点的值一次遍历完成创建中间对象存储每一项的数据,然后获取每一项数据的子节点 (时间复杂度较低 O(n))

  • 遍历过程中对象形式存储每一项数据(key为数据唯一标识,value是当前的每一项数据),根据传入的Tree数据的总节点的父节点判断是否是Tree数据的总结点,如果当前数据不是总节点则将数据插入到temptree对应父节点的数据中,如果当前数据是总节点则将temptree中对应当前数据key的值插入最终的数据中(temptree中的数据有子节点数据)
/*
* data: array 平铺格式的数据
* firstTreeKey: string | number Tree数据的总节点的父节点的值
* uniqueKey: string 数组中每一项唯一标识所对应的属性
* relativeKey: string 数组中每一项关联父节点所对应的属性
*/

const generateTree = (data, firstTreeKey, uniqueKey, relativeKey) => {
  const treeData = []; // 最后返回的tree结构数据
  const temptree = {}; // 对象形式存储每一项数据(key为数据唯一标识,value是当前的每一项数据)
  for(const item of data) {
    const id = item[uniqueKey];
    const parentId = item[relativeKey];
    temptree[id] = {...temptree[id], ...item};
    const treeItem = temptree[id]
    if (parentId === firstTreeKey) {
      treeData.push(treeItem)
    } else {
      if(!temptree[parentId]) {
        temptree[parentId] = {};
      }
      if(!temptree[parentId].children) {
        temptree[parentId].children = [];
      }
      temptree[parentId].children.push(treeItem)
    }
  }
  return treeData;
}

// 获取Tree数据的总节点的父节点的值
const firstTreeKey = getFirstTreeKey(data, 'value', 'parentId');

const treeData = generateTree(data, firstTreeKey, 'value', 'parentId');