如何将父子关系的数据转化为树形结构的数组
这篇文章其实是跟上一篇微信小程序-树形菜单替代方案-路径视图组件相关的一篇文章,因为该组件希望传入的是一种树形结构的数据,然而通常我们从后台获取到的数据只是具有父子关系的普通数组。
形如下方: [{"id":1,"title":"1"},{"id":2,"pid":1,"title":"2"}]
可能希望得到的数据如下: [{"id":1,"title":"1","children":[{"id":2,"pid":1,"title":"2"}]}]
这篇文章介绍就是如何用javascript实现这种转换:
思路
原始数据的数组成员均带有父级信息,一个父级成员可以是多个子级成员的父级,但一个子级成员永远只有一个父级。这听上去有点拗口,但细细品味应该还是挺好理解的。那么很显然,子级找父级是相对容易的,因为子级知道父级的信息,而且只有一个父级。但是要做成树形结构的数组,就应该是父级知晓子级的信息,以便从*一级一级地访问下去找到某个子级
所以我转化的思路就有点像小蝌蚪找爸爸。为什么不是妈妈?事实上如果你喜欢也可以这么理解,反正我们也不关心它是父是母。每个数据成员找到爸爸后,告诉爸爸:爸爸爸爸,我的的地址是在哪里哪里,从此这个爸爸就知道自己有这个子女,它是住在这个地方的,它就可以随时找到这个子女了。(嗯,这些爸爸心就这么大,别人说是它女儿,它就信的,一点都不怀疑。。)这样子每个父亲都知道自己的子信息了,我们只要有辈分最大的那些爸爸的地址,就可以一级一级地找到任意的子级了。
比如我想问辈分最大的某个爸爸,就可以获知所有子级的地址,拜访某个子级a地址找到该子级,又可以从子级a获知所有子级a的子级们的地址了。这就形成了一个树状的信息结构。
原理
对数组进行遍历,让某个数组都找父级,找到父级后就将其本身存入父级的fatherKey字段里,由于对象都是相对引用,这里就相对于告诉父级子级的地址。也就是在父级内存入了子级的地址。大家可以想象一下,父级有了子级的地址,然后子级的家里(对象)又存入了它对应的子级们的地址,这样子那些本身没有父级的父级(即最*的父级)就可以通过地址去到下一级的家里,在这个家里找到地址,再直奔它的孙子家里,一直往下。
完善
根据上面的原理已经可以实现代码了,但是还可以进行部分优化,事实上,这个部分是我实现了代码之后进行的,但为了大家能轻松的理解我的代码,将这个过程移到展示代码之前:
首先,数组中的对象是相对引用,这个给了我们转化极大的便利,但是也正因为这样子的一个原因,当我们修改传入的这个数组时,原数组也会被修改,而这可能不是我们所希望的,因此要在转化之前先将传入的数组进行深度拷贝为一个新的数组里,再对这个新的数组进行操作。
然后,因为要每个数组成员都要和其他成员确认它是否是自己的父亲,因此需要进行二次遍历,这里显然成员是不需要和自己确认的,毕竟不可能自己是自己的父级。
接着,当子级找到父级后就不需要和其他成员确认了,因为它只有一个父级,而确认的目的就是要找到父级,目标达成,所以没有进行下去的必要。
最后,有些参数理应由外部传入,因为这些值不同项目需求可能会有差异,但是为了可以更大程度的简化操作,也为这些值配置了默认值
例如:
- 每个成员用来记录父级信息的键名是什么
- 标识其自身id的键名是什么
- 希望存放子级信息的键名是什么
- 根级元素其标识父级信息的键值是什么
…
代码
function toTree({
value = [],
fatherKey = 'pid',
selfKey = 'id',
childrenKey = 'children',
rootValue = undefined
} = {}) {
// 复制对象
value = JSON.parse(JSON.stringify(value));
for (let i = 0; i < value.length; i++) {
let itemI = value[i];
if (itemI[fatherKey] === rootValue) {
continue;
}
for (let j = 0; j < value.length; j++) {
if (i === j) {
continue;
}
let itemJ = value[j];
if (itemI[fatherKey] === itemJ[selfKey]) {
if (
!itemJ[childrenKey] ||
Object.prototype.toString.call(itemJ[childrenKey]) !==
'[object Array]'
) {
itemJ[childrenKey] = [];
}
itemJ[childrenKey].push(itemI);
break;
}
}
}
return value.filter(item => item[fatherKey] === rootValue);
}
本文参考了js将有父子关系的数据转换成树形结构数据中u014613927用户的实现。