• JS前端树形Tree数据结构使用


    前端开发中会经常用到树形结构数据,如多级菜单、商品的多级分类等。数据库的设计和存储都是扁平结构,就会用到各种Tree树结构的转换操作,本文就尝试全面总结一下。

    如下示例数据,关键字段id为唯一标识,pid父级id,用来标识父级节点,实现任意多级树形结构"pid": 0“0”标识为根节点,orderNum属性用于控制排序。

    1. const data = [
    2.   { "id": 1, "name": "用户中心", "orderNum": 1, "pid": 0 },
    3.   { "id": 2, "name": "订单中心", "orderNum": 2, "pid": 0 },
    4.   { "id": 3, "name": "系统管理", "orderNum": 3, "pid": 0 },
    5.   { "id": 12, "name": "所有订单", "orderNum": 1, "pid": 2 },
    6.   { "id": 14, "name": "待发货", "orderNum": 1.2, "pid": 2 },
    7.   { "id": 15, "name": "订单导出", "orderNum": 2, "pid": 2 },
    8.   { "id": 18, "name": "菜单设置", "orderNum": 1, "pid": 3 },
    9.   { "id": 19, "name": "权限管理", "orderNum": 2, "pid": 3 },
    10.   { "id": 21, "name": "系统权限", "orderNum": 1, "pid": 19 },
    11.   { "id": 22, "name": "角色设置", "orderNum": 2, "pid": 19 },
    12. ];

    在前端使用的时候,如树形菜单、树形列表、树形表格、下拉树形选择器等,需要把数据转换为树形结构数据,转换后的数据结效果图:

    预期的树形数据结构:多了children数组存放子节点数据。

    1. const treeData = [
    2. { "id": 1, "name": "用户中心", "pid": 0 },
    3. {
    4. "id": 2, "name": "订单中心", "pid": 0,
    5. "children": [
    6. { "id": 12, "name": "所有订单", "pid": 2 },
    7. { "id": 14, "name": "待发货", "pid": 2 },
    8. { "id": 15, "name": "订单导出","pid": 2 }
    9. ]
    10. },
    11. {
    12. "id": 3, "name": "系统管理", "pid": 0,
    13. "children": [
    14. { "id": 18, "name": "菜单设置", "pid": 3 },
    15. {
    16. "id": 19, "name": "权限管理", "pid": 3,
    17. "children": [
    18. { "id": 21, "name": "系统权限", "pid": 19 },
    19. { "id": 22, "name": "角色设置", "pid": 19 }
    20. ]
    21. }
    22. ]
    23. }
    24. ]

    列表转树-list2Tree

    方法一 递归遍历

    从根节点递归,查找每个节点的子节点,直到叶子节点(没有子节点)

    1. //递归函数,pid默认0为根节点
    2. function listToTree(items, pid = 0) {
    3. //查找pid子节点
    4. let pitems = items.filter(s => s.pid === pid)
    5. if (!pitems || pitems.length <= 0)
    6. return null
    7. //递归
    8. pitems.forEach(item => {
    9. const res = listToTree(items, item.id)
    10. if (res && res.length > 0)
    11. item.children = res
    12. })
    13. return pitems
    14. }
    方法二 object的Key遍历

    简单理解就是一次性循环遍历查找所有节点的父节点,两个循环就搞定了。

    • 第一次循环,把所有数据放入一个Object对象map中,id作为属性key,这样就可以快速查找指定节点了。
    • 第二个循环获取根节点、设置父节点。

    分开两个循环的原因是无法完全保障父节点数据一定在前面,若循环先遇到子节点,map中还没有父节点的,否则一个循环也是可以的。

    1. /**
    2. * 集合数据转换为树形结构。option.parent支持函数,示例:(n) => n.meta.parentName
    3. * @param {Array} list 集合数据
    4. * @param {Object} option 对象键配置,默认值{ key: 'id', parent: 'pid', children: 'children' }
    5. * @returns 树形结构数据tree
    6. */
    7. export function listToTree(list, option = { key: 'id', parent: 'pid', children: 'children' }) {
    8. let tree = []
    9. // 获取父编码统一为函数
    10. let pvalue = typeof (option.parent) === 'function' ? option.parent : (n) => n[option.parent]
    11. // map存放所有对象
    12. let map = {}
    13. list.forEach(item => {
    14. map[item[option.key]] = item
    15. })
    16. //遍历设置根节点、父级节点
    17. list.forEach(item => {
    18. if (!pvalue(item))
    19. tree.push(item)
    20. else {
    21. map[pvalue(item)][option.children] ??= []
    22. map[pvalue(item)][option.children].push(item)
    23. }
    24. })
    25. return tree
    26. }

    树转列表-tree2List

    从上而下依次遍历,把所有节点都放入一个数组中即可

    1. /**
    2. * 树形转平铺list(广度优先,先横向再纵向)
    3. * @param {*} tree 一颗大树
    4. * @param {*} option 对象键配置,默认值{ children: 'children' }
    5. * @returns 平铺的列表
    6. */
    7. export function tree2List(tree, option = { children: 'children' }) {
    8. const list = []
    9. const queue = [...tree]
    10. while (queue.length) {
    11. const item = queue.shift()
    12. if (item[option.children]?.length > 0)
    13. queue.push(...item[option.children])
    14. list.push(item)
    15. }
    16. return list
    17. }

    搜索过滤树-filterTree

    基本思路:

    • 为避免污染原有Tree数据,这里的对象都使用了简单的浅拷贝const newNode = { ...node }
    • 递归为主的思路,子节点有命中,则会包含父节点,当然父节点的children会被重置。
    1. /**
    2. * 递归搜索树,返回新的树形结构数据,只要子节点命中保留其所有上级节点
    3. * @param {Array|Tree} tree 一颗大树
    4. * @param {Function} func 过滤函数,参数为节点对象
    5. * @param {Object} option 对象键配置,默认值{ children: 'children' }
    6. * @returns 过滤后的新 newTree
    7. */
    8. export function filterTree(tree, func, option = { children: 'children' }) {
    9. let resTree = []
    10. if (!tree || tree?.length <= 0) return null
    11. tree.forEach(node => {
    12. if (func(node)) {
    13. // 当前节点命中
    14. const newNode = { ...node }
    15. if (node[option.children])
    16. newNode[option.children] = null //清空子节点,后面递归查询赋值
    17. const cnodes = filterTree(node[option.children], func, option)
    18. if (cnodes && cnodes.length > 0)
    19. newNode[option.children] = cnodes
    20. resTree.push(newNode)
    21. }
    22. else {
    23. // 如果子节点有命中,则包含当前节点
    24. const fnode = filterTree(node[option.children], func, option)
    25. if (fnode && fnode.length > 0) {
    26. const newNode = { ...node, [option.children]: null }
    27. newNode[option.children] = fnode
    28. resTree.push(newNode)
    29. }
    30. }
    31. })
    32. return resTree
    33. }

  • 相关阅读:
    朴素贝叶斯
    虚拟机的发展史:从分时系统到容器化
    可视化容器管理工具-portainer.io使用
    职场必看!性能测试响应很慢怎么排查?
    MySQL-HMA 高可用故障切换
    阿里云访问资源:NoSuchKey
    R语言计算dataframe数据中特定(指定)数据列中的前一个值与后一个值的差值
    认识主被动无人机遥感数据、预处理无人机遥感数据、定量估算农林植被关键性状、期刊论文插图精细制作与Appdesigner应用开发
    flink的CoProcessFunction使用示例
    Java开发学习(三十)----Maven聚合和继承解析
  • 原文地址:https://blog.csdn.net/weixin_41848005/article/details/133312959