(一)前言
对于大部分网站需求,我们经常会遇到一种需求,将list转为tree结构,或者将tree转为list结构,或者我们在tree结构中查找对于的数据,返回,或者返回顶层到查找级别的list。
(二)将list转为tree
我们一般会在后台数据库设计时候,当我们设计省市县联动时候,一般会在数据库设计唯一索引id,和pid字段,pid关联父级别id,这样当查询出来的数据很可能是类似下面的结构
const data = [
{ id: 1, pid: 0, name: 'no.1' },
{ id: 2, pid: 1, name: 'no.2' },
{ id: 3, pid: 1, name: 'no.3' },
{ id: 4, pid: 1, name: 'no.4' },
{ id: 5, pid: 1, name: 'no.5' },
{ id: 6, pid: 2, name: 'no.6' },
{ id: 7, pid: 2, name: 'no.7' },
{ id: 8, pid: 2, name: 'no.8' },
{ id: 9, pid: 3, name: 'no.9' },
{ id: 10, pid: 3, name: 'no.10' },
{ id: 11, pid: 3, name: 'no.11' },
{ id: 12, pid: 4, name: 'no.12' },
{ id: 13, pid: 4, name: 'no.13' },
{ id: 14, pid: 13, name: 'no.14' },
];
但是实际上大部分UI展示需要生成tree形结构,方便UI暂时,这时候我们应该怎么办呢,你可以写一个arrayToTree方法,类似如下
function arrayToTree(list, pid = 0) {
return list.filter(item => item.pid === pid).map(item => ({
...item,
children: arrayToTree(list, item.id),
}));
}
// 调用
console.log(arrayToTree(data));
那么调用和输出结构就如下
我们可以看到已经成功将list转为tree。
(三)将tree转为list
我们直接使用上面生成的tree结构,如下,来将它扁平化成一维数组
const data = [
{
id: 1,
pid: 0,
name: 'no.1',
children: [
{
id: 2,
pid: 1,
name: 'no.2',
children: [
{ id: 6, pid: 2, name: 'no.6' },
{ id: 7, pid: 2, name: 'no.7' },
{ id: 8, pid: 2, name: 'no.8' },
],
},
{
id: 3,
pid: 1,
name: 'no.3',
children: [
{ id: 9, pid: 3, name: 'no.9' },
{ id: 10, pid: 3, name: 'no.10' },
{ id: 11, pid: 3, name: 'no.11' },
],
},
{
id: 4,
pid: 1,
name: 'no.4',
children: [
{ id: 12, pid: 4, name: 'no.12' },
{
id: 13,
pid: 4,
name: 'no.13',
children: [
{ id: 14, pid: 13, name: 'no.14' },
],
},
],
},
{ id: 5, pid: 1, name: 'no.5' },
],
},
];
现在我们来写treeToArray方法,因为生成list会根据当前层级对于扁平化,所以需要对list使用sort作依次排序,那么我们就需要一个compareProps的方法来生成序列化的列表
function compareProps(key) {
return (obj1, obj2) => {
const val1 = obj1[key];
const val2 = obj2[key];
if (val1 < val2) {
return -1;
}
if (val1 > val2) {
return 1;
}
return 0;
};
}
function treeToArray(list, newArr = []) {
list.forEach((item) => {
const { children } = item;
if (children) {
delete item.children;
if (children.length) {
newArr.push(item);
return treeToArray(children, newArr);
}
}
newArr.push(item);
});
return newArr;
}
console.log(treeToArray(data).sort(compareProps('id')));
从上面结果我们可以看到,目前数据结构变为如下了。
(三)查询子元素
list如果需要使用id查询一个列表中对象,简单就提供find,findIndex api,所以这里不做介绍,我们来总结在如果知道一个id,如何在tree得到其所在层级或者之前上级到顶层的关系。
这里我实现来一个getTreePathList方法,我先上代码,再来为大家介绍相关参数
/**
* 递归查询tree path
* @param {array} list 数组
* @param {string} value 比对值
* @param {object} options 配置
* {
* {String} equalKey 比对key
* {String} returnKey 返回key
* {Bool} returnIndex 是否返回index
* {Bool} returnOnlyLast 只返回最后一个对象
* }
* @return {*} 从顶层到当前层级
*/
// 这里list是需要查询的愿tree,
// value是查询的值,
// 第三个参数为配置项,需要注意就是如果returnOnlyLast为true,只会返回搜索元素当前层的object
function getTreePathList(list, value, {
equalKey = 'name',
returnKey = 'uid',
returnIndex = false,
returnOnlyLast = false,
} = {}) {
for (let i = 0; i < list.length; i += 1) {
const { children = [], [equalKey]: name, [returnKey]: uid } = list[i];
const returnMap = (returnIndex || returnOnlyLast) ? {
...list[i],
index: i,
} : uid;
if (name === value) {
if (returnOnlyLast) return returnMap;
return [returnMap];
}
if (children && children.length) {
const res = getTreePathList(children, value, {
equalKey,
returnKey,
returnIndex,
returnOnlyLast,
});
if (res) {
if (returnOnlyLast) return res;
return [returnMap, res].flat() || [];
}
}
}
}
那么我们需要父级的就这样使用
console.log(getTreePathList(data, 14, {
equalKey: 'id',
returnIndex: true,
}));
这里我们可以看到,第一个参数是列表list,第二个是查找的值,第三个是个对象提供equalKey, returnKey, returnIndex, returnOnlyLast。首先equalKey是比对的key,returnKey当returnIndex/returnOnlyLast为false时候,返回的key,类似这种[name1, name2, name3, …], 最后分别是returnIndex,会将每次循环的index返回,这个可能在做搜索滚动距离需要,returnOnlyLast就是表示这时候 只会返回找到的对象。
(四)修改tree结构字段
当我们后端返回tree结构时候,常规场景,会需要将数据修改为ui框架需要的数据结构,这时候你就需要一个方法来处理数据
- 方案1
// 迭代器
function iterator(arr, cb, isFirst = true) {
for (let i = 0; i < arr.length; i += 1) {
/* eslint-disable no-continue */
// 这是做状态过滤
// if (arr[i].status !== 1) {
// arr.splice(i, 1);
// i -= 1;
// continue;
// }
cb.call(arr[i], arr[i], arr.length === 1 && isFirst);
}
}
// 处理每一项
function changeItem(item, isFirst) {
// 更改字段名称
item.isFirst = isFirst;
item.key = item.id;
item.title = item.name;
item.tags_children = item.children || [];
if (item.children) {
iterator(item.children, changeItem, false);
delete item.children;
}
}
terator(data, changeItem);
console.log(data);
我们可以看到原始data里面children已经改为tags_children,新增来key,title, isFirst字段。
- 方案2 不在外面包装迭代器
function changeTreeKey(list, isFirst = true) {
return list.map((item) => {
item.isFirst = isFirst;
item.key = item.id;
item.title = item.name;
item.tags_children = item.children || [];
if (item.children) {
delete item.children;
changeTreeKey(item.tags_children, false);
}
return item;
});
}
console.log(changeTreeKey(data));
和上面一样,我们可以看到对于数据已经被修改。