在遇到如下类型的树状结构列表时,在新增、修改、删除的时候需要即时更新列表。
数据结构一览:
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": "文章热度统计"
}
]
}
]
}
]
那么有以下几种方案可以实现:
- 刷新页面
- 再次向服务器发起请求,更新数据
- 直接更改此次列表数据,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;
}
以上三个函数,应该可以解决大部分的树状结构数据相关的业务代码
关于树状结构数据的更多操作,这里推荐一篇文章:JS树结构操作:查找、遍历、筛选、树结构和列表结构相互转换 - 沐码小站