[关闭]
@MickeyWang 2017-05-11T19:33:26.000000Z 字数 3686 阅读 461

关于树形插件展示中数据结构转换的算法

JavaScript

Laughing的博客
Laughing四儿


本文目录:


问题背景

在一些目录结构、机构层级等展示的场景中,我们经常会用到一些成熟的树形插件来进行轻松展示,比如ztree等。大多数插件会支持对两种数据源格式的解析,一种是通用的二维数据结构,一种是树状数据结构。对于这两种数据结构的称呼在各插件中可能不尽相同,这里依照二维结构和树状结构来称呼。举例说明如下:

  1. // 二维数据结构
  2. [{
  3. "id": "001",
  4. "name": "总部",
  5. "parentId": "0"
  6. }, {
  7. "id": "002",
  8. "name": "二级门店1",
  9. "parentId": "001"
  10. }, {
  11. "id": "003",
  12. "name": "三级门店",
  13. "parentId": "002"
  14. }, {
  15. "id": "004",
  16. "name": "二级门店2",
  17. "parentId": "001"
  18. }]
  19. // 树状数据结构
  20. [{
  21. "id": "001",
  22. "name": "总部",
  23. "parentId": "0",
  24. "children": [{
  25. "id": "002",
  26. "name": "二级门店1",
  27. "parentId": "001",
  28. "children": [{
  29. "id": "003",
  30. "name": "三级门店",
  31. "parentId": "002",
  32. "children": []
  33. }]
  34. }, {
  35. "id": "004",
  36. "name": "二级门店2",
  37. "parentId": "001",
  38. "children": []
  39. }]
  40. }]

但在某些插件中,或在某些特殊场景中,我们有两种数据结构之间相互转换的需求,需要自己写一个辅助函数来完成。这里就提供两个这样的工具函数来完成数据结构的转换。

Note: 要说明的是,工具函数没有经过大数据量转换测试,所以对有实时性、大量源数据转换需求的同学而言,请自行测试分析,可采取前置或异步等方案处理。由于自身技术水平的局限性,算法本身会有性能优化的空间,若有更优处理算法,还望交流分享,谢谢!

解决方案

我们来分开介绍两种数据结构之间的转换算法,每个小结中我会先贴出整个函数的代码清单,以便大家复制粘贴,然后会简要说明其中大概的逻辑思路。

二维数据结构 => 树状数据结构

  1. /**
  2. * 将通用的二维数据结构转换为树状数据结构
  3. * @param {String} rootParentIdValue 表示根节点的父类id值
  4. * @param {String} parentIdName 表示父类id的节点名称
  5. * @param {String} nodeIdName 表示二维结构中,每个对象主键的名称
  6. * @param {Array} listData 为二维结构的数据
  7. * @return {Array} 转换后的tree结构数据
  8. */
  9. function listToTree(rootParentIdValue, parentIdName, nodeIdName, listData) {
  10. if (listData instanceof Array && listData.length > 0 && listData[0][parentIdName]) {
  11. var rootList = [],
  12. nodeList = []
  13. listData.forEach(function(node, index) {
  14. if (node[parentIdName] == rootParentIdValue) {
  15. rootList.push(node);
  16. } else {
  17. nodeList.push(node);
  18. }
  19. });
  20. if (nodeList.length > 0 && rootList.length > 0) {
  21. childrenNodeAdd(rootList, nodeList);
  22. return rootList;
  23. } else if (rootList.length > 0) {
  24. throw new Error("没有对应的子节点集合");
  25. } else {
  26. throw new Error("没有对应的父类节点集合");
  27. }
  28. function childrenNodeAdd(rootNodeList, childrenList) {
  29. if (childrenList.length > 0) {
  30. rootNodeList.forEach(function(rootNode) {
  31. rootNode["children"] = [];
  32. var childrenNodeList = childrenList.slice(0);
  33. childrenList.forEach(function(childrenNode, childrenIndex) {
  34. if (parentIdName in childrenNode && rootNode[nodeIdName] == childrenNode[parentIdName]) {
  35. rootNode["children"].push(childrenNode);
  36. childrenNodeList.splice(childrenIndex, 1);
  37. }
  38. });
  39. childrenNodeAdd(rootNode["children"], childrenNodeList);
  40. });
  41. }
  42. }
  43. } else {
  44. throw new Error("格式不正确,无法转换");
  45. }
  46. }

此函数可通过listToTree("0", "parentId", "id", sourceData)调用测试,sourceData为文章开头给出的二维数据结构举例。

下面简要介绍一下其中逻辑,第10行是简要验证一下入参数据的合法性,然后声明了rootList和nodeList两个变量。其中rootList为顶级根节点,nodeList为其他子节点集合,第14行到20行的循环便是为两个变量赋值,之后根据两个变量的值进一步判断数据的合法性。在验证之后调用childrenNodeAdd这个内部函数,此函数之后将会被递归调用,为每一个节点添加指定名称为“children”的子节点数组。两个入参分别是rootNodeList和childrenList,代表父节点集合,和其之后的所有子节点集合。在23行第一次调用时,传入的便是顶级根节点和其之后的所有子孙节点。下面看这段带有详细注解的代码片段:

  1. function childrenNodeAdd(rootNodeList, childrenList) {
  2. if (childrenList.length > 0) { // 如果没有子节点了就结束递归
  3. //遍历父节点集合,在子节点中查找其自身的子节点,并添加到对应的子节点数组中
  4. rootNodeList.forEach(function(rootNode) {
  5. rootNode["children"] = [];
  6. var childrenNodeList = childrenList.slice(0); //复制一个子节点数据,用于存放剩余的子节点
  7. //遍历所有子节点
  8. childrenList.forEach(function(childrenNode, childrenIndex) {
  9. if (parentIdName in childrenNode && rootNode[nodeIdName] == childrenNode[parentIdName]) { //根节点的id 等于子节点的父类id
  10. rootNode["children"].push(childrenNode); //添加对应节点归为子节点
  11. childrenNodeList.splice(childrenIndex, 1);//在剩余子节点中剔除已经分配过的子节点
  12. }
  13. });
  14. childrenNodeAdd(rootNode["children"], childrenNodeList); //剩余子节点继续递归执行,每次递归一次就表示节点增加一级。
  15. });
  16. }
  17. }

树状数据结构 => 二维数据结构

  1. /**
  2. * 将树状数据结构转换为二维数据结构
  3. * @param {String} childrenName 树状结构中子节点名称
  4. * @param {Array} treeData 树状结构数据
  5. * @return {Array} 转换后的通用二维结构数据
  6. */
  7. function treeToList(childrenName, treeData) {
  8. var listData = [];
  9. transferTreeData(treeData);
  10. function transferTreeData (sourceData) {
  11. sourceData.forEach( function(node, nodeIndex) {
  12. if(node[childrenName].length > 0)
  13. transferTreeData(node[childrenName]);
  14. delete node[childrenName];
  15. listData.push(node);
  16. });
  17. }
  18. return listData;
  19. }

此函数可通过treeToList("children", sourceData)调用测试,sourceData为文章开头给出的树状数据结构举例。这里的逻辑比较简单就不再赘述了。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注