树形处理方法及项目场景中实用

424 阅读4分钟

JS中你可能需要知道的树形处理方法

在前端日常的开发当中,我们其实还是比较容易与树形结构打交道的。常见的比如,我们需要根据后端返回的菜单列表生成页面中树形菜单。也有可能是将列表数据转换成树形选择器的选项数据等。也有可能是从树形的数据当中找到某个数据等。

那么下面我将我在项目当中使用的处理方法分享出来。

1. 将列表转换成树形结构

1.1 我的思路

这种思路的前提条件是所有的父节点都在在子节点的前面出现。首先循环遍历列表,将每一项都放入一个map中,同时判断每一项的父节点是否在map对象中,如果存在就将这一项放入到这个父节点的children属性下面。这样循环结束就可以生成树形的数据了。

image.png

1.2 代码实现


const createTree = function (list=[]) {
    let tree = [];
    let map = {};
    list.forEach(item => {
        const newItem = {
            id: item.id,
            pid: item.pid,
            name: item.name,
            code: item.code,
            children: [],
        };

        map[item.id] = newItem;
        // 判断是否是根节点
        if (item.pid === -1) {
            tree.push(newItem);
        } else {
            map[newItem.pid].children.push(newItem);
        }
    });
    return tree;
};

const list = [
    {
        id: 1,
        pid: -1,
        name: '张三1',
        code: '1'
    },
    {
        id: 11,
        pid: 1,
        name: '张三11',
        code: '11'
    },
    {
        id: 12,
        pid: 1,
        name: '张三12',
        code: '12'
    },
    {
        id: 13,
        pid: 1,
        name: '张三13',
        code: '13'
    },
    {
        id: 2,
        pid: -1,
        name: '张三2',
        code: '2'
    },
    {
        id: 21,
        pid: 2,
        name: '张三2',
        code: '21'
    },
    {
        id: 211,
        pid: 21,
        name: '张三211',
        code: '211'
    },
]

console.log(createTree(list));


1.3 升级改造

我们上面的方法是有个前提条件的那就是父节点必须在子节点之前出现,否则就会出现问题。那么如何处理父节点在子节点之后出现呢。

我们可以再加个map,用于存放那些父节点还没出现的子节点,然后循环遍历的时候判断这个暂存区是否有自己的子节点,如果有就将他们放入到自己的children属性里面。

image.png

代码如下

const createTree = function (list=[]) {
    const tree = [];
    const map = {};
    const waitPushObj = {};
    list.forEach(item => {
        const newItem = {
            id: item.id,
            pid: item.pid,
            name: item.name,
            code: item.code,
            children: [],
        };

        map[item.id] = newItem;
        // 判断是否是根节点
        if (item.pid === -1) {
            tree.push(newItem);
        } else if (map[item.pid]) {
            map[item.pid].children.push(newItem);
        } else {
            if (!waitPushObj[item.pid]) {
                waitPushObj[item.pid] = [];
            }
            waitPushObj[item.pid].push(newItem);
        }

        // 判断暂存区是否有自己的子节点
        if (waitPushObj[item.id]) {
            newItem.children = waitPushObj[item.id];
            delete waitPushObj[item.id];
        }

        // 可以不加这个判断
        if (!map[item.id]) {
            map[item.id] = newItem;
        }
    });
    return tree;
};

const list = [
    {
        id: 11,
        pid: 1,
        name: '张三11',
        code: '11'
    },
    {
        id: 12,
        pid: 1,
        name: '张三12',
        code: '12'
    },
    {
        id: 13,
        pid: 1,
        name: '张三13',
        code: '13'
    },
    {
        id: 21,
        pid: 2,
        name: '张三2',
        code: '21'
    },
    {
        id: 1,
        pid: -1,
        name: '张三1',
        code: '1'
    },
    {
        id: 211,
        pid: 21,
        name: '张三211',
        code: '211'
    },
    {
        id: 2,
        pid: -1,
        name: '张三2',
        code: '2'
    },
]

console.log(createTree(list));

2. 查找某个元素

2.1 利用迭代的方式查找

const tree = [
    {
        "id": 1,
        "pid": -1,
        "name": "张三1",
        "code": "1",
        "children": [
            {
                "id": 11,
                "pid": 1,
                "name": "张三11",
                "code": "11",
                "children": []
            },
            {
                "id": 12,
                "pid": 1,
                "name": "张三12",
                "code": "12",
                "children": []
            },
            {
                "id": 13,
                "pid": 1,
                "name": "张三13",
                "code": "13",
                "children": []
            }
        ]
    },
    {
        "id": 2,
        "pid": -1,
        "name": "张三2",
        "code": "2",
        "children": [
            {
                "id": 21,
                "pid": 2,
                "name": "张三2",
                "code": "21",
                "children": [
                    {
                        "id": 211,
                        "pid": 21,
                        "name": "张三211",
                        "code": "211",
                        "children": []
                    }
                ]
            }
        ]
    }
]

const findNode = function (tree, id) {
    const list = [...tree];
    let p = list.shift();
    while (p) {
        if (p.id === id) return p;
        if (p.children) {
            list.push(...p.children)
        }
        p = list.shift();
    }
    return null;
}

console.log(findNode(tree, 211))

项目场景中

 * 把平铺的数组结构转成树形结构
 *
 * [
 *  {id:"01", pid:"",   "name":"老王" },
 *  {id:"02", pid:"01", "name":"小张" }
 * ]
 * 上面的结构说明: 老王是小张的上级
 */
// 方法一有缺陷
export function tranListToTreeData1(list) {
  // 1. 定义两个变量
  const treeList = []; const map = {}

  // 2. 建立一个映射关系,并给每个元素补充children属性.
  // 映射关系: 目的是让我们能通过id快速找到对应的元素
  // 补充children:让后边的计算更方便
  list.forEach(item => {
    if (!item.children) {
      item.children = []
    }
    map[item.id] = item
  })

  // 循环
  list.forEach(item => {
    // 对于每一个元素来说,先找它的上级
    //    如果能找到,说明它有上级,则要把它添加到上级的children中去
    //    如果找不到,说明它没有上级,直接添加到 treeList
    const parent = map[item.pid]
    // 如果存在上级则表示item不是最顶层的数据
    if (parent) {
      parent.children.push(item)
    } else {
      // 如果不存在上级 则是顶层数据,直接添加
      treeList.push(item)
    }
  })
  // 返回
  return treeList
}

// 方法二递归写法
export function tranListToTreeData(list, pid) {
  var newList = []
  list.forEach(item => {
    if (item.pid === pid) {
      var child = tranListToTreeData(list, item.id)
      // console.log(child.length)
      if (child) {
        item.children = child
      }
      newList.push(item)
    }
  })
  return newList
}