Tree和array之间相互转换

(一)前言
对于大部分网站需求,我们经常会遇到一种需求,将list转为tree结构,或者将tree转为list结构,或者我们在tree结构中查找对于的数据,返回,或者返回顶层到查找级别的list。

(二)将list转为tree
我们一般会在后台数据库设计时候,当我们设计省市县联动时候,一般会在数据库设计唯一索引id,和pid字段,pid关联父级别id,这样当查询出来的数据很可能是类似下面的结构

const data = [
  { id: 1, pid: 0, name: 'no.1' },
  { id: 2, pid: 1, name: 'no.2' },
  { id: 3, pid: 1, name: 'no.3' },
  { id: 4, pid: 1, name: 'no.4' },
  { id: 5, pid: 1, name: 'no.5' },
  { id: 6, pid: 2, name: 'no.6' },
  { id: 7, pid: 2, name: 'no.7' },
  { id: 8, pid: 2, name: 'no.8' },
  { id: 9, pid: 3, name: 'no.9' },
  { id: 10, pid: 3, name: 'no.10' },
  { id: 11, pid: 3, name: 'no.11' },
  { id: 12, pid: 4, name: 'no.12' },
  { id: 13, pid: 4, name: 'no.13' },
  { id: 14, pid: 13, name: 'no.14' },
];

但是实际上大部分UI展示需要生成tree形结构,方便UI暂时,这时候我们应该怎么办呢,你可以写一个arrayToTree方法,类似如下

function arrayToTree(list, pid = 0) {
  return list.filter(item => item.pid === pid).map(item => ({
    ...item,
    children: arrayToTree(list, item.id),
  }));
}

// 调用
console.log(arrayToTree(data));

那么调用和输出结构就如下
在这里插入图片描述
我们可以看到已经成功将list转为tree。

(三)将tree转为list

我们直接使用上面生成的tree结构,如下,来将它扁平化成一维数组

const data = [
  {
    id: 1,
    pid: 0,
    name: 'no.1',
    children: [
      {
        id: 2,
        pid: 1,
        name: 'no.2',
        children: [
          { id: 6, pid: 2, name: 'no.6' },
          { id: 7, pid: 2, name: 'no.7' },
          { id: 8, pid: 2, name: 'no.8' },
        ],
      },
      {
        id: 3,
        pid: 1,
        name: 'no.3',
        children: [
          { id: 9, pid: 3, name: 'no.9' },
          { id: 10, pid: 3, name: 'no.10' },
          { id: 11, pid: 3, name: 'no.11' },
        ],
      },
      {
        id: 4,
        pid: 1,
        name: 'no.4',
        children: [
          { id: 12, pid: 4, name: 'no.12' },
          {
            id: 13,
            pid: 4,
            name: 'no.13',
            children: [
              { id: 14, pid: 13, name: 'no.14' },
            ],
          },
        ],
      },
      { id: 5, pid: 1, name: 'no.5' },
    ],
  },
];

现在我们来写treeToArray方法,因为生成list会根据当前层级对于扁平化,所以需要对list使用sort作依次排序,那么我们就需要一个compareProps的方法来生成序列化的列表

function compareProps(key) {
  return (obj1, obj2) => {
    const val1 = obj1[key];
    const val2 = obj2[key];
    if (val1 < val2) {
      return -1;
    }
    if (val1 > val2) {
      return 1;
    }
    return 0;
  };
}

function treeToArray(list, newArr = []) {
  list.forEach((item) => {
    const { children } = item;
    if (children) {
      delete item.children;

      if (children.length) {
        newArr.push(item);
        return treeToArray(children, newArr);
      }
    }
    newArr.push(item);
  });
  return newArr;
}

console.log(treeToArray(data).sort(compareProps('id')));

从上面结果我们可以看到,目前数据结构变为如下了。

array列表(三)查询子元素

list如果需要使用id查询一个列表中对象,简单就提供find,findIndex api,所以这里不做介绍,我们来总结在如果知道一个id,如何在tree得到其所在层级或者之前上级到顶层的关系。
这里我实现来一个getTreePathList方法,我先上代码,再来为大家介绍相关参数

/**
 * 递归查询tree path
 * @param {array} list 数组
 * @param {string} value 比对值
 * @param {object} options 配置
 * {
 *   {String} equalKey 比对key
 *   {String} returnKey 返回key
 *   {Bool} returnIndex 是否返回index
 *   {Bool} returnOnlyLast 只返回最后一个对象
 * }
 * @return {*} 从顶层到当前层级
 */
 // 这里list是需要查询的愿tree,
 // value是查询的值,
 // 第三个参数为配置项,需要注意就是如果returnOnlyLast为true,只会返回搜索元素当前层的object
function getTreePathList(list, value, {
  equalKey = 'name',
  returnKey = 'uid',
  returnIndex = false,
  returnOnlyLast = false,
} = {}) {
  for (let i = 0; i < list.length; i += 1) {
    const { children = [], [equalKey]: name, [returnKey]: uid } = list[i];
    const returnMap = (returnIndex || returnOnlyLast) ? {
      ...list[i],
      index: i,
    } : uid;
    if (name === value) {
      if (returnOnlyLast) return returnMap;
      return [returnMap];
    }
    if (children && children.length) {
      const res = getTreePathList(children, value, {
        equalKey,
        returnKey,
        returnIndex,
        returnOnlyLast,
      });
      if (res) {
        if (returnOnlyLast) return res;
        return [returnMap, res].flat() || [];
      }
    }
  }
}

那么我们需要父级的就这样使用

console.log(getTreePathList(data, 14, {
  equalKey: 'id',
  returnIndex: true,
}));

这里我们可以看到,第一个参数是列表list,第二个是查找的值,第三个是个对象提供equalKey, returnKey, returnIndex, returnOnlyLast。首先equalKey是比对的key,returnKey当returnIndex/returnOnlyLast为false时候,返回的key,类似这种[name1, name2, name3, …], 最后分别是returnIndex,会将每次循环的index返回,这个可能在做搜索滚动距离需要,returnOnlyLast就是表示这时候 只会返回找到的对象。

(四)修改tree结构字段
当我们后端返回tree结构时候,常规场景,会需要将数据修改为ui框架需要的数据结构,这时候你就需要一个方法来处理数据

  1. 方案1
// 迭代器
function iterator(arr, cb, isFirst = true) {
  for (let i = 0; i < arr.length; i += 1) {
    /* eslint-disable no-continue */
    // 这是做状态过滤
    // if (arr[i].status !== 1) {
    //   arr.splice(i, 1);
    //   i -= 1;
    //   continue;
    // }
    cb.call(arr[i], arr[i], arr.length === 1 && isFirst);
  }
}

// 处理每一项
function changeItem(item, isFirst) {
  // 更改字段名称
  item.isFirst = isFirst;
  item.key = item.id;
  item.title = item.name;
  item.tags_children = item.children || [];

  if (item.children) {
    iterator(item.children, changeItem, false);
    delete item.children;
  }
}

terator(data, changeItem);
console.log(data);

我们可以看到原始data里面children已经改为tags_children,新增来key,title, isFirst字段。

  1. 方案2 不在外面包装迭代器
function changeTreeKey(list, isFirst = true) {
  return list.map((item) => {
    item.isFirst = isFirst;
    item.key = item.id;
    item.title = item.name;
    item.tags_children = item.children || [];

    if (item.children) {
      delete item.children;
      changeTreeKey(item.tags_children, false);
    }
    return item;
  });
}

console.log(changeTreeKey(data));

和上面一样,我们可以看到对于数据已经被修改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章