预排序遍历树和JSON互转

1,101 阅读4分钟

预排序遍历树算法和JSON互转

预排序遍历树算法简介

预排序遍历树算法简称mptt,主要应用于层级关系的存储和遍历。

MTTP 不直接存储父分类信息,而是通过 left、right 配合depth来表示层级关系。

  • 优点
    • 查询效率高,只需要一次查询即可获得层级结构中某个节点的所有子节点,无需递归查询
  • 缺点
    • 插入、删除、移动节点效率较低,需要修改当前节点后面所有节点的左右值

由于公司数据库采用的是这样的一种格式存储,返回给前端的是一维的数组,列表展示是完全没有问题的,但在前端某些特殊情况下需要json的嵌套格式来展示数据,所以需要将mptt转为json,而搜索一番没找到相应的实现,遂自己实现了mptt树和json的互转,如有需要的同学可以对代码进行适当的更改以适应自己项目。

MTTT 转 JSON
const mptToJson = (arr) => {
  if (!Array.isArray(arr)) {
    throw Error('mpt to json parameter must be a array');
  }
  // 根据depth来,存放层级关系,
  let stack = [];
  // 存放嵌套json
  let result = [];
  // 结果的map结构
  let resultMap = {};
  // 上一次遍历的节点
  let last = null;
  // 引用当前遍历的节点的子节点,递归的时候使用
  let children = null;
  function judgeDepth(item) {
    // 没有last,说明是第一次进入,一定是一级节点
    if (!last) {
      // stack放入该节点
      stack.push(item);
      // result存放一级节点
      result.push(item);
      // 设置上一次的调用接待室
      last = item;
      // children设置为result,表示一级节点的父级是初始化的数组
      children = result;
      
      // 不是第一次进入,说明last一定有值
    } else {
      // 当前节点和上一个节点depth相同,说明是同级节点
      if (item.depth === last.depth) {
        // stack层级移除最后一个节点
        stack.pop();
        // 获取stack的最后一层,stack存放的是父子层级
        last = stack[stack.length - 1];
        // 如果有last,说明当前节点是有父节点的
        if (last) {
          // 引用父节点的children
          children = last.children!;
          // 设置当前节点pid
          item.pid = last.id;
        } else {
          // 否则childre就是最外层数组
          children = result;
        }
        // chilren push当前节点
        children.push(item);
        // stack层级添加当前节点
        stack.push(item);
        // 设置last为当前节点
        last = item;
       // 当前节点depth比上一个节点depth大,说明是上一个节点的子节点
      } else if (item.depth > last.depth) {
        // 引用上一个节点的children
        children = last.children;
        children!.push(item);
        // 设置pid
        item.pid = last.id!;
        // 深度增加一层,stack存放当前节点
        stack.push(item);
        last = item;
      } else {
        // 这时表示既不是同级节点也不是父子节点,depth后退了一层
        stack.pop();
        // 重新设置last节点
        last = stack[stack.length - 1];
        // 重新调用该函数
        judgeDepth(item);
      }
    }
  }
  function eachArr(nodeArr) {
    nodeArr.forEach((item) => {
      // 在map结构中根据id来存放每一个节点,避免循环数组来查找节点
      resultMap[item.id] = item;
      // 给每一个节点增加children属性,用于json嵌套使用
      item.children = [];
      // 节点父id,用于嵌套中表示节点父子关系
      item.pid = undefined;
      // 对每一个节点调用函数
      judgeDepth(item);
    });
  }
  
  eachArr(arr);
  return {
    result,
    resultMap,
  };
};
JSON 转 MPTT
const jsonToMpt = (arrData) => {
  if (!Array.isArray(arrData)) {
    throw Error('json to mpt parameter must be a array');
  }
  // 我这里设置left值是从2开始的,根节点是单独存储的
  let left = 2;
  // 存放结果
  let result = [];
  function eachArr(arr) {
    let i = 0;
    let len = arr.length;
    for (i; i < len; i++) {
      // 获取每一个json数组的节点
      let item = arr[i];
      // 设置当前节点左值
      item.leftValue = left++;
      // result存放当前节点
      result.push(item);
      // 当前节点有子节点
      if (item.children && item.children.length > 0) {
        // 递归对当前节点的子节点数组调用该函数
        eachArr(item.children);
      }
      // 递归结束后当前的节点的右值就是正确的右值了
      item.rightValue = left++;
    }
  }
  eachArr(arrData);
  return result;
};

FAQ

  1. MPTT转json后增加、删除怎么做

    如果对实时性要求非常高的话,可以获取到当前节点的Pid,只需要修改父节点及之后节点的左右值即可,但这样前端等待时间比较长,如果前端实时性要求比较高的话可以把操作放在前端待用户操作一定时间后统一的保存到后端,这样需要区分哪些数据是新添加的,哪些是删除的。

  2. MPTT树一定要转json展示吗

    MPTT查询根据左值排序后是一个一维的已排序好数组,可以根据depth来实现层级展示