let arr = [ { id: '1', title: '节点1', children: [ { id: '1-1', title: '节点1-1' }, { id: '1-2', title: '节点1-2' } ] }, { id: '2', title: '节点2', children: [ { id: '2-1', title: '节点2-1' } ] } ] /** 广度优先遍历 **/ function breadthFisrstSearch(tree, cb) { let node = null let list = [...tree] while (node = list.shift()) { cb(node) node.children && list.push(...node.children) } } breadthFisrstSearch(arr, (data) => { data.text = data.id.toString() + data.title console.log(data) }) /** ======================== 深度优先遍历 ======================== **/ /** 先序遍历 **/ function depthFirstTraversal(tree, cb) { tree.forEach(item => { cb(item) item.children && depthFirstTraversal(item.children, cb) }) } /** 后序遍历 **/ function depthFirstTraversalEnd(tree, cb) { tree.forEach(item => { item.children && depthFirstTraversalEnd(item.children, cb) cb(item) }) } /** 先序遍历 循环版 **/ function depthFirstTraversalWithLoop(tree, cb) { let node = null let list = [...tree] while (node = list.shift()) { cb(node) node.children && list.unshift(...node.children) } } /** 后序遍历 循环版 **/ function depthFirstTraversalEndWithLoop(tree, cb) { let node = null let list = [...tree] let i = 0 while (node = list[i]) { let len = node.children ? node.children.length : 0 if (!len || list[i - 1] === node.children[len - 1]) { cb(node) i++ } else { list.splice(i, 0, ...node.children) } } } /** 数据过滤 **/ function treeFilter(tree, func) { // 使用map复制一下节点,避免修改到原树 return tree.map(node => ({ ...node })).filter(node => { node.children = node.children && treeFilter(node.children, func) return func(node) || (node.children && node.children.length) }) } /** 数据查找 **/ function treeFind(tree, cb) { for (const data of tree) { if (cb(data)) return data if (data.children) { const res = treeFind(data.children, cb) if (res) return res } } return null } /** 节点路径 **/ function treeFindPath(tree, cb, path = []) { if (!tree) return [] for (const data of tree) { path.push(data.id) if (cb(data)) return path if (data.children) { const findChildren = treeFindPath(data.children, cb, path) if (findChildren.length) return findChildren } path.pop() } return [] }
let list = [ { id: '1', title: '节点1', parentId: '', }, { id: '1-1', title: '节点1-1', parentId: '1' }, { id: '1-2', title: '节点1-2', parentId: '1' }, { id: '2', title: '节点2', parentId: '' }, { id: '2-1', title: '节点2-1', parentId: '2' } ] function ArrayToTree(arr) { let tree = [] let map = {} arr.forEach(item => { item.children = [] map[item.id] = item }) arr.forEach(item => { let parent = map[item.parentId] if (parent) { parent.children.push(item) } else { tree.push(item) } }) return tree } function arrayToTreeWithReduce(arr) { let map = arr.reduce((map, item) => { item.children = [] map[item.id] = item return map }, {}) return arr.filter(item => { map[item.parentId] && map[item.parentId].children.push(item) return !item.parentId }) } //递归实现 function treeToList(tree, result = [], level = 0) { tree.forEach(node => { result.push(node) node.level = level + 1 node.children && treeToList(node.children, result, level + 1) }) return result } // 循环实现 function treeToList(tree) { let node, result = tree.map(node => (node.level = 1, node)) for (let i = 0; i < result.length; i++) { if (!result[i].children) continue let list = result[i].children.map(node => (node.level = result[i].level + 1, node)) result.splice(i + 1, 0, ...list) } return result }
数据结构
data = [ { "id": 1, "pid": 0, "font_route": "", "api_action": "", "is_menu": 1, "is_show": 0, "title": "数据统计", "children": [ { "id": 2, "pid": 1, "font_route": "/main/ActivityList", "api_action": "Index/getActivityList", "is_menu": 1, "is_show": 1, "title": "文章列表", "children": [ { "id": 6, "pid": 2, "font_route": "/main/ArticleHot", "api_action": "Index/getArticleHot", "is_menu": 0, "is_show": 1, "title": "文章热度统计" } ] } ] } ]
那么有以下几种方案可以实现:
1.刷新页面
2.再次向服务器发起请求,更新数据
3.直接更改此次列表数据,vue中会自动刷新视图
第一、第二种方案都需要再次发送请求与服务端交互,比较浪费带宽与效率。这里重点说下第三种直接修改本次列表数据的方法。
直接修改树状结构的话,常用的方法就是递归操作,通过递归实现树状结构的新增、删除、查找。
新增对象
直接上基础代码:【以id作为关键词,children作为下级字段】
const handleData = (id, data, obj) => { data.forEach(item => { if (item.id === id) { item.children ? item.children.push(obj) : item.children = [obj] } else { if (item.children) { handleData(id, item.children, obj) } } }) return data }
改写可以自定义关键词、自定义下级字段的函数:
function insertTreeListItem (treeList, key , item , childField ,keyField) { var childField = arguments[3] ? arguments[3] : 'children' var keyField = arguments[4] ? arguments[4] : 'id' treeList.forEach(treeitem => { if (treeitem[keyField] === key) { treeitem[childField] ? treeitem[childField].push(item) : treeitem[childField] = [item] } else { if (treeitem[childField]) { insertTreeListItem(treeitem[childField], key , item , childField ,keyField) } } }) return treeList }
使用vue的请注意:
在vue中如果直接使用以上代码修改原数据的话,那么你会发现数据虽然更新了,但是有时候视图并没有更新。细心的小伙伴应该很快发现了,其中有对象的直接赋值语句。
总所周知,在vue2中直接的对象赋值时无法更新视图的。在本案例中由于是公共函数,也不可能在函数中直接使用this.$set赋值。解决方案有两个:
1、让后台为每一个item都加上children的子项目,这样前端就不用使用对象赋值了。
2、前端不直接传入data的值,通过深拷贝得到一个数据,再重新赋值回data
这里推荐使用第二种方法:
删除对象
删除对象写起来比较简单,本质上也是递归。删除可以不用返回新数据,直接影响原数组也问题不打,需要返回新对象的可以直接改写一下。
基础函数:
function removeTreeListItem(treeList, id) { // 根据id属性从数组(树结构)中移除元素 if (!treeList || !treeList.length) { return } for (let i = 0; i < treeList.length; i++) { if (treeList[i].id === id) { treeList.splice(i, 1); break; } removeTreeListItem(treeList[i].children, id) } }
改写成可以自定义关键词、自定义下级字段的函数
function removeTreeListItem(treeList, key , childField ,keyField) { var childField = arguments[2] ? arguments[2] : 'children' var keyField = arguments[3] ? arguments[3] : 'id' if (!treeList || !treeList.length) { return } for (let i = 0; i < treeList.length; i++) { if (treeList[i][keyField] === key) { treeList.splice(i, 1); break; } removeTreeListItem(treeList[i][childField], key ,childField ,keyField) } }
查找对象
基础函数:
getObjByTree = (data, id) => { let result = null if (!data) return // return; 中断执行 for (let i in data) { if (result !== null) break let item = data[i]; if (item.id=== id) { result = item; break; } else if (!!item?.children?.length) { result = this.getObjByTree(item.children, id); } } return result; }
改写成可以自定义关键词、自定义下级字段的函数
function searchTree(treeList, key , childField ,keyField){ var childField = arguments[2] ? arguments[2] : 'children' var keyField = arguments[3] ? arguments[3] : 'id' let result = null if (!treeList) return // return; 中断执行 for (let i in treeList) { if (result !== null) break let item = treeList[i]; if (item[keyField]=== key) { result = item; break; } else if ( item[childField] && item[childField].length>0 ) { result = searchTree(item[childField], key); } } return result; }
以上三个函数,应该可以解决大部分的树状结构数据相关的业务代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏