南昌网站设计专业,wordpress 您的主题支持1个菜单,创新创业大赛项目计划书,网站开发的优势前端开发中会经常用到树形结构数据#xff0c;如多级菜单、商品的多级分类等。数据库的设计和存储都是扁平结构#xff0c;就会用到各种Tree树结构的转换操作#xff0c;本文就尝试全面总结一下。 
如下示例数据#xff0c;关键字段id为唯一标识#xff0c;pid为父级id如多级菜单、商品的多级分类等。数据库的设计和存储都是扁平结构就会用到各种Tree树结构的转换操作本文就尝试全面总结一下。 
如下示例数据关键字段id为唯一标识pid为父级id用来标识父级节点实现任意多级树形结构。pid: 0“0”标识为根节点orderNum属性用于控制排序。 
const data  [{ id: 1, name: 用户中心, orderNum: 1, pid: 0 },{ id: 2, name: 订单中心, orderNum: 2, pid: 0 },{ id: 3, name: 系统管理, orderNum: 3, pid: 0 },{ id: 12, name: 所有订单, orderNum: 1, pid: 2 },{ id: 14, name: 待发货, orderNum: 1.2, pid: 2 },{ id: 15, name: 订单导出, orderNum: 2, pid: 2 },{ id: 18, name: 菜单设置, orderNum: 1, pid: 3 },{ id: 19, name: 权限管理, orderNum: 2, pid: 3 },{ id: 21, name: 系统权限, orderNum: 1, pid: 19 },{ id: 22, name: 角色设置, orderNum: 2, pid: 19 },
]; 
在前端使用的时候如树形菜单、树形列表、树形表格、下拉树形选择器等需要把数据转换为树形结构数据转换后的数据结效果图 预期的树形数据结构多了children数组存放子节点数据。 
const treeData  [{ id: 1, name: 用户中心, pid: 0 },{id: 2, name: 订单中心, pid: 0,children: [{ id: 12, name: 所有订单, pid: 2 },{ id: 14, name: 待发货, pid: 2 },{ id: 15, name: 订单导出,pid: 2 }]},{id: 3, name: 系统管理, pid: 0,children: [{ id: 18, name: 菜单设置, pid: 3 },{id: 19, name: 权限管理, pid: 3,children: [{ id: 21, name: 系统权限,  pid: 19 },{ id: 22, name: 角色设置,  pid: 19 }]}]}
]列表转树-list2Tree 
方法一 递归遍历 
从根节点递归查找每个节点的子节点直到叶子节点没有子节点 
//递归函数pid默认0为根节点
function listToTree(items, pid  0) {//查找pid子节点let pitems  items.filter(s  s.pid  pid)if (!pitems || pitems.length  0)return null//递归pitems.forEach(item  {const res  buildTree(items, item.id)if (res  res.length  0)item.children  res})return pitems
}方法二 object的Key遍历 
简单理解就是一次性循环遍历查找所有节点的父节点两个循环就搞定了。 
第一次循环把所有数据放入一个Object对象map中id作为属性key这样就可以快速查找指定节点了。第二个循环获取根节点、设置父节点。 
分开两个循环的原因是无法完全保障父节点数据一定在前面若循环先遇到子节点map中还没有父节点的否则一个循环也是可以的。 
/*** 集合数据转换为树形结构。option.parent支持函数示例(n)  n.meta.parentName* param {Array} list 集合数据* param {Object} option 对象键配置默认值{ key: id, parent: pid, children: children }* returns 树形结构数据tree*/
export function listToTree(list, option  { key: id, parent: pid, children: children }) {let tree  []// 获取父编码统一为函数let pvalue  typeof (option.parent)  function ? option.parent : (n)  n[option.parent]// map存放所有对象let map  {}list.forEach(item  {map[item[option.key]]  item})//遍历设置根节点、父级节点list.forEach(item  {if (!pvalue(item))tree.push(item)else {map[pvalue(item)][option.children] ?? []map[pvalue(item)][option.children].push(item)}})return tree
} 
树转列表-tree2List 
从上而下依次遍历把所有节点都放入一个数组中即可 
/*** 树形转平铺list广度优先先横向再纵向* param {*} tree 一颗大树* param {*} option 对象键配置默认值{ children: children }* returns 平铺的列表*/
export function tree2List(tree, option  { children: children }) {const list  []const queue  [...tree]while (queue.length) {const item  queue.shift()if (item[option.children]?.length  0)queue.push(...item[option.children])list.push(item)}return list
}搜索过滤树-filterTree 
基本思路 
为避免污染原有Tree数据这里的对象都使用了简单的浅拷贝const newNode  { ...node }。递归为主的思路子节点有命中则会包含父节点当然父节点的children会被重置。 
/*** 递归搜索树返回新的树形结构数据只要子节点命中保留其所有上级节点* param {Array|Tree} tree 一颗大树* param {Function} func 过滤函数参数为节点对象* param {Object} option 对象键配置默认值{ children: children }* returns 过滤后的新 newTree*/
export function filterTree(tree, func, option  { children: children }) {let resTree  []if (!tree || tree?.length  0) return nulltree.forEach(node  {if (func(node)) {// 当前节点命中const newNode  { ...node }if (node[option.children])newNode[option.children]  null //清空子节点后面递归查询赋值const cnodes  filterTree(node[option.children], func, option)if (cnodes  cnodes.length  0)newNode[option.children]  cnodesresTree.push(newNode)}else {// 如果子节点有命中则包含当前节点const fnode  filterTree(node[option.children], func, option)if (fnode  fnode.length  0) {const newNode  { ...node, [option.children]: null }newNode[option.children]  fnoderesTree.push(newNode)}}})return resTree
}