菜单的递归算法

617 阅读3分钟

菜单的递归算法

场景

原本以为前端开发算法遇到的机会很少,但是世事无常,大肠包小肠,今天却被算法卡住了去路。场景是这样的:管理后台中需要通过菜单和当前的访问路由路径去生成面包屑。

示例:

  //有以下树形树形结构
let list = [        {          icon: 'inbox',          label: '菜单1',          children: [            {              icon: 'perm_identity',              label: '菜单1-1',              path: '/index'            },            {              icon: 'delete',              label: '菜单1-2',              children: [                {                  icon: 'settings',                  label: '菜单1-2-1',                  path: '/index2'                }              ]
            }
          ]
        },
        {
          icon: 'feedback',
          label: '菜单2',
          path: '/test',
          hidden: false,
          children: [
                {
                  icon: 'settings',
                  label: '菜单2-1',
                  path: '/2121'
                },
                 {
                  icon: 'settings',
                  label: '菜单2-2',
                  path: '/2121'
                }
              ]
        }
      ]    
    
//预期结果:
输入:/index2
返回:[{"icon": "inbox","label": "菜单1","children": [....]},
      {"icon": "delete","label": "菜单1-2","children": [...]},
      {"icon": "settings","label": "菜单2-1","path": "/index2"}]
    

分析

    1. 输入的数据解构是一个树形状数据解构。
    1. 通过path去匹配所符合的所有父级以及自身那一项。
    1. 字段path的值不可能重复。

看到类似的数据解构第一个想到的就是使用递归函数去做这件事。从结果来看,获取到的是一个扁平化的数组。所以这边需要用到一个栈。传入一个用来匹配的字符串,和一个菜单。 此时得到一个大体的框架:


export function getMenuBred (currentLabel, menus) {
  let stack = []
  function _deep(currentLabel, menus) {
      _deep(currentLabel,....)
  }
  _deep(currentLabel, menus)
}

现在能想到的大体方法是把每一个的循环的数据全部给添加到这个stack数组中。再对其中的数据进行筛选之类的操作。而此时,递归函数应该就是每一项遍历菜单的children。那么_deep这个递归函数里面有了以下操作:

export function getMenuBred (currentLabel, menus) {
  let stack = []
  function _deep(currentLabel, menus) {
     for (let index = 0; index < menus.length; index++) {
            const menu = menus[index]
            stack.push(menu)
             _deep(currentLabel,menu.children)
      }
   } 
  _deep(currentLabel, menus)
}
   

那么这个时候stack中会得到一个扁平化的数组。

stack =[菜单1,菜单1-1,菜单1-2,菜单2......]

以上的基本很多人写到这里就会想,如何去除没有用的项呢?不一定要在数据全部完成时去除选项,应该在在有循环进行不下去的时候就可以去除这个选项,比如:

  • 没有children的条件下去除当前对象
  • 如果知道子集返回给上级,子集的条件不满足,那么去除选项。

以上两个条件这就是整个递归的核心了。

export function getMenuBred (currentLabel, menus) {
  let stack = []
  function _deep(currentLabel, menus) {
     for (let index = 0; index < menus.length; index++) {
            const menu = menus[index]
            stack.push(menu)
            if (menu.children && menu.children.length) { //有children对象则继续执行
                if (_deep(currentLabel, menu.children)) {
                    return true
                } else {
                    stack.pop()
                }
            } else {//没有children的条件下去除当前对象
                stack.pop()
            }
      }
   } 
  _deep(currentLabel, menus)
}

利用递归函数返回的值去判断是否子集能够符合条件,这是一个比较难以想到的步骤,这里就慢慢意会了。

代码走到这里还缺少一些判断字符串的逻辑。补齐所有代码,大功告成!

完整代码


export function getMenuBred (currentLabel, menus) {
  let stack = []
  function _deep(currentLabel, menus) {
    for (let index = 0; index < menus.length; index++) {
        const menu = menus[index]
        stack.push(menu)
        if (menu.path === currentLabel) {
            return true
        }
        if (menu.children && menu.children.length) {
            if (_deep(currentLabel, menu.children)) {
                return true
            } else {
                stack.pop()
            }
        } else {
            stack.pop()
        }
    }
    return false
  }
  const isMatch = _deep(currentLabel, menus)
  return isMatch ? stack : []
}