Why
本题场景为多层菜单渲染,后端返回的数组型数据,前端生成树形结构
- 后端数据数组,方便在数据库的表结构中存储
- 前端需要树型结构,因为菜单项往往要封装封装逻辑和样式,需要递归渲染
What
示例:
let arr = [
{id: 1, name: '菜单1', pid: 0},
{id: 2, name: '菜单2', pid: 1},
{id: 3, name: '菜单3', pid: 1},
{id: 4, name: '菜单4', pid: 3},
{id: 5, name: '菜单5', pid: 4},
]
转换为
[
{
"id": 1,
"name": "菜单1",
"pid": 0,
"children": [
{
"id": 2,
"name": "菜单2",
"pid": 1,
"children": []
},
{
"id": 3,
"name": "菜单3",
"pid": 1,
"children": [
// ...
]
}
]
}
]
How
直接写法
先来跟着直觉写一下,声明结果数组,for 循环跑起来
const result = []
for(let item of arr) {}
每循环到一个时,都要从 result 里看看有没有父元素,有的话就放进去,顺便加上 children 字段
const result = []
for(let item of arr) {
for(let child of result) {
if (child.id === item.pid) {
child.children.push({...item, children: []})
}
}
}
这时观察数据发现有可能是多层的,所以还要加上 children 的判断,加上后发现循环嵌套了
const result = []
for(let item of arr) {
// 父节点的循环
for(let child of result) {
if (child.id === item.pid) {
child.children.push({...item, children: []})
} else if (child.children.length > 0) {
// 子节点的循环
for(let nextchild of child.children) {
if (nextchild.id === item.pid) {
nextchild.children.push({...item, children: []})
} else if (nextchild.children.length > 0) {
// _(¦3」∠)_
}
}
}
}
}
要解决嵌套的循环,此时必然掏出酝酿已久的递归,把看起来长得一样的部分提出来一个函数,根据循环里做的事情(给 item 在 result 中找到父节点),形象地起一个函数名
// 循环查找函数
function findAFather(item, list) {
for(let child of list) {
if (child.id === item.pid) {
child.children.push({...item, children: []})
} else if (child.children.length > 0) {
findAFather(item, child.children)
}
}
}
const result = []
for(let item of arr) {
findAFather(item, result)
}
OK 大功告成!
等等 Σ(⊙▽⊙"a),如果没找到怎么办?em...
要判断一下查找结果,方法里返回查找结果
function findAFather(item, list) {
for(let child of list) {
if (child.id === item.pid) {
child.children.push({...item, children: []})
// 找到后返回 true
return true
} else if (child.children.length > 0) {
findAFather(item, child.children)
}
}
// 没找到返回 false
return false
}
const result = []
for(let item of arr) {
findAFather(item, result)
}
每次循环也要把结果带出去
function findAFather(item, list) {
for(let child of list) {
if (child.id === item.pid) {
child.children.push({...item, children: []})
return true
} else if (child.children.length > 0) {
// 返回下层循环结果
return findAFather(item, child.children)
}
}
return false
}
const result = []
for(let item of arr) {
// 收到查找结果
const nice = findAFather(item, result)
}
没找到的话,他自己也可能是个 father
function findAFather(item, list) {
for(let child of list) {
if (child.id === item.pid) {
child.children.push({...item, children: []})
return true
} else if (child.children.length > 0) {
return findAFather(item, child.children)
}
}
return false
}
const result = []
for(let item of arr) {
const nice = findAFather(item, result)
// 没找到就直接添加到结果中
if (!nice) {
result.push({...item, children: []})
}
}
完成!✿✿ヽ(°▽°)ノ✿
Next
优化写法
利用“对象专一的特性”,直接把数组平铺成对象,就可以把复杂的递归查找做成简单的取值查找
let arr = [
{id: 1, name: '菜单1', pid: 0},
{id: 2, name: '菜单2', pid: 1},
{id: 3, name: '菜单3', pid: 1},
{id: 4, name: '菜单4', pid: 3},
{id: 5, name: '菜单5', pid: 4},
]
const map = {}
for (let item of arr) {
map[item.id] = {...item, children: []}
}
const result = []
for(let item of arr) {
if (map[item.pid]) {
map[item.pid].children.push(map[item.id])
} else {
result.push(map[item.id])
}
}
console.log(result)