一、简介
有时候后端返回的树形结构数据是未处理的,就是一个数组,这个时候就需要前端自己来做处理。
二、后端返回的数据结构
[{
id: '511681000042',
name: '父节点01',
parentId: ''
},
{
id: '01',
name: '子阶段01',
parentId: '511681000042'
},
{
id: '02',
name: '子节点02',
parentId: '511681000042'
},
{
id: '03',
name: '子节点03'
parentId: '511681000042'
}]
三、将上面的数据转化为树形结构
/*
思路:树形结构都是分层的,这里做法是每次递归将每一层的数据筛选出来,然后组装树形结构,减少递归次数
*/
/*
@params parentIds 父节点id数组
@params treeData 将要转化为树形结构的数据
@params transformItemDataFunc 转化某个数据的函数(可不传)
*/
const handleTransformTreeData = function (parentIds = [], treeData = [], transformItemDataFunc) {
// 判断数据类型
let toString = Object.prototype.toString;
if (toString.call(parentIds) !== '[object Array]') {
return new Error('handleTransformTreeData: 父节点值必须为数组');
}
if (toString.call(treeData) !== '[object Array]') {
return new Error('handleTransformTreeData: 节点数据必须为数组');
}
if (transformItemDataFunc != void 0 &&
toString.call(transformItemDataFunc) !== '[object Function]') {
return new Error('handleTransformTreeData: transformItemDataFunc必须为函数');
}
// 父节点数据Map
let treeMap = {};
// 子节点数据
let children = [];
// 下次参与筛选的数据
let nextTreeData = [];
// 1. 分离父节点和参与下一次递归的子节点数据
treeData.forEach(item => {
if (parentIds.includes(item.parentId)) {
treeMap[item.id] = transformItemDataFunc === void 0
? item
: transformItemDataFunc(item);
} else {
nextTreeData.push(item);
}
});
// 2. 将分离后的父节点id当作下一次筛选的parentIds
parentIds = Object.keys(treeMap);
// 这里判断父节点和子阶段个数
// 如果父节点或则子节点个数为零个,则不满足继续递归条件
if (parentIds.length > 0 && nextTreeData.length > 0) {
// 3. 从children里面筛选出tree的子节点数据
children = handleTransformTreeData(parentIds, nextTreeData);
// 4. 关联tree 和 children数据
children.length > 0 &&
children.forEach(item => {
let { parentId } = item;
if (treeMap[parentId].children === void 0) {
treeMap[parentId].children = [];
}
treeMap[parentId].children.push(item);
});
}
return Object.keys(treeMap).map(id => {
return treeMap[id];
});
}
四、获取节点路径
/*
思路:和生成树形结构的思路一样,每次递归都在同一层找路径,直到找到为止
*/
/*
@params nodes 要找到路径的节点id
@params tree 树形结构数据
@isFirstLayer 是否为第一层
*/
const handleGetTreePath = function (nodes = [], tree = [], isFirstLayer = true) {
let toString = Object.prototype.toString;
if (toString.call(nodes) !== '[object Array]') {
throw new Error('handleGetTreePath: 节点数据必须为数组');
}
if (toString.call(tree) !== '[object Array]') {
throw new Error('handleGetTreePath: 树形结构必须为数组');
}
// 如果是第一层,则初始化path
tree = isFirstLayer
? tree.map(item => ({ ...item, path: [item.id] }))
: tree;
let currentTreeMap = {};
let nextTree = []; // 下一层要循环的数据
let path = []; // 找到的节点路径
let notFindNodes = []; // 未找到的节点
tree.forEach(item => {
// 当前层数据
currentTreeMap[item.id] = item;
// 下一层数据
nextTree = nextTree.concat(item.children === void 0 ? [] : item.children.map(it => {
let pPath = JSON.parse(JSON.stringify(item.path));
pPath.push(it.id);
return {
...it,
path: pPath
}
}));
});
// 区分找到的节点和未找到的节点数据
nodes.forEach(id => {
currentTreeMap[id] !== void 0
? path.push(currentTreeMap[id].path) : notFindNodes.push(id);
});
if (notFindNodes.length > 0 && nextTree.length > 0) {
let findPath = handleGetTreePath(notFindNodes, nextTree, false);
path = path.concat(findPath);
}
return path;
}
五、测试结果
5.1 生成树形结构(以上面的数据为例)
image.png
5.2 节点路径查找 (以上面生成数据为例)
image.png
image.png