菜单的递归算法
场景
原本以为前端开发算法遇到的机会很少,但是世事无常,大肠包小肠,今天却被算法卡住了去路。场景是这样的:管理后台中需要通过菜单和当前的访问路由路径去生成面包屑。
示例:
//有以下树形树形结构
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"}]
分析
-
- 输入的数据解构是一个树形状数据解构。
-
- 通过path去匹配所符合的所有父级以及自身那一项。
-
- 字段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 : []
}