在现实天下中,有时候,后端返回的唯一性标识是classId,deptId等,而不是id。有时候,后端返回的父id叫pId,而不是parentId。以是,为了灵巧性,我们不能将id和parentId写去世。
现在,我们创建toTree函数,该函数的入参定义如下,data是由有关联的一些工具构成的一维数组;config包含2个字段,id是自定义id字段的别名,默认为id;pId是自定义父id的别名,默认为parentId。
/ @param {any[]} data @param {{id: string, parentId: string}} config /export const toTree = (data, config) => { const { id = 'id', parentId: pId = 'parentId' } = config || {}}
要把一维数组转化为树,我们须要先找到最顶层的树枝节点。假定我们的数据最顶层节点没有确定性的标识,我们该如何确定一个节点是否顶级节点呢?

我们须要将数据data中所有的id单独拿出来,构成一个id组成的数组ids。
const ids = data.map(_ => _[id])
然后,我们先创建2个空数组,一个用于存储结果的数组result,一个用于存储待检测数据的数组waitChecked,后面会用到。
const result = []const waitChecked = []
然后,遍历数据data,如果ids中包含当前数据项的父id,那么这个数据项一定不是顶级树叶节点。否则,一定是顶级树叶节点。我们将顶级树叶节点放入result数组,将其它节点放入waitChecked数组。
data.forEach(_ => (ids.includes(_[pId]) ? waitChecked : result).push(_))
然后,我们创建一个对result数组的浅拷贝flatData。当转化完成后,flatData数组将变成一颗平铺的树,result数组将变成一棵树。
const flatData = [...result]
现在,我们开始实行转化操作。我们采取了while循环,直到waitChecked数组变为空,退出循环。这部分代码比较繁芜,每行都加入了注释,大家直接看注释就行,就不在代码之外阐明了。
while (waitChecked.length) { // 平铺树的节点id构成的数组 const flatIds = flatData.map(_ => _[id]) // 取出一个父节点在平铺树中的节点 const node = waitChecked.find(_ => flatIds.includes(_[pId])) // 从平铺树中获取node的父节点 const parNode = flatData.find(_ => _[id] === node[pId]) // 从待检测数组中移除节点 waitChecked.splice(waitChecked.indexOf(node), 1) if (parNode.children) { // 如果存在children属性,直接push子节点 parNode.children.push(node) } else { // 否则,新增该属性,并把子节点node添加进去 parNode.children = [node] } // 将子节点node推入平铺树 flatData.push(node) } return result // 这便是最终生成的树
现在我们用一些数据测试一下toTree函数。
const json = [ { id: 1, parentId: -1, name: 'menu-1' }, { id: 3, parentId: 2, name: 'menu-1 item-1' }, { id: 2, parentId: 1, name: 'menu-1-1' }, { id: 4, parentId: -1, name: 'menu-2' }, { id: 5, parentId: 4, name: 'menu-2 item-1' }]console.log(JSON.stringify(toTree(json), null, 2))/ 输出:[ { "id": 1, "parentId": -1, "name": "menu-1", "children": [ { "id": 2, "parentId": 1, "name": "menu-1-1", "children": [ { "id": 3, "parentId": 2, "name": "menu-1 item-1" } ] } ] }, { "id": 4, "parentId": -1, "name": "menu-2", "children": [ { "id": 5, "parentId": 4, "name": "menu-2 item-1" } ] }]/
末了,我们把以上代码整合在一起,完全的toTree函数定义如下:
/ @param {any[]} data @param {{id: string, parentId: string}} config /export const toTree = (data, config) => { const { id = 'id', parentId: pId = 'parentId' } = config || {} const ids = data.map(_ => _[id]) const result = [] const waitChecked = [] data.forEach(_ => (ids.includes(_[pId]) ? waitChecked : result).push(_)) const flatData = [...result] while (waitChecked.length) { const flatIds = flatData.map(_ => _[id]) const node = waitChecked.find(_ => flatIds.includes(_[pId])) const parNode = flatData.find(_ => _[id] === node[pId]) waitChecked.splice(waitChecked.indexOf(node), 1) if (parNode.children) { parNode.children.push(node) } else { parNode.children = [node] } flatData.push(node) } return result}
如果大家有更好的办理方案,欢迎分享在评论区;如果你是初学者,有不懂的地方,也可以在评论区留言;本篇文章到此结束,感谢大家的阅读!