树形结构数据的查询、渲染和删除是一类常见的问题。
两个月前有个初级前端卡在这个需求。现有数据结构如下:
const data = [{
id: 9,
title: 'Node2',
}, {
id: 1,
title: 'Node1',
children: [{
id: 2,
title: 'Child Node1',
}, {
id: 3,
title: 'Child Node2',
}, {
id: 4,
title: 'Child Node2',
children: [{
id: 5,
title: 'Child Node1',
}, {
id: 6,
title: 'Child Node2',
}, {
id: 7,
title: 'Child Node7',
},]
},]
}, {
id: 8,
title: 'Node2'
}]
如何查询指定id下的data?
我给出递归的方案:
const findOne = (_data, id) => {
let i = 0;
let result = null;
while (i < _data.length) {
if (_data[i].id == id) {
result = _data[i];
break;
} else if (_data[i].children&& findOne(_data[i].children, id)) {
return findOne(_data[i].children, id);
} else {
i++;
}
}
return result
}
//测试
console.log(findOne(data, 5))
函数将返回指定id下的所有数据。
项目以 antD
为例:
这个数据结构,除了章节节点之外还有习题,最初后端给出的是两个表联查得出的数据结构:
习题放在了和children同层级的resource中。children对应的id为value,resource对应的为resourceid。
而标准的渲染,是必须把习题也放入到children中的。
// 渲染树形结构
renderTree(arr, parentNode) {
let cHtml = <div></div>;
let _this = this;
arr = arr.map((x, i) => {
if (x.children) {
x.children = x.children.concat(x.rescourse)
// console.log(x)
return <TreeNode
key={x.value}
title={<span>{x.label} <a style={{
color: 'rgb(255,150,50)'
}} href="javascript:;" onClick={(e) => {
e.stopPropagation();
this.nodeoOnEdit(x)
}}><Icon type="edit" /></a>
<a style={{
color: 'rgb(241,102,82)'
}} href="javascript:;" onClick={(e) => {
e.stopPropagation();
this.nodeoOnDel(x)
}}><Icon type="delete" /></a>
</span>} >{_this.renderTree(x.children, x.value)}</TreeNode>
} else {
return <TreeNode
key={arr[i].value}
// key={parentNode}
title={<span>{arr[i].title} <a style={{
color: 'rgb(241,102,82)'
}} href="javascript:;" onClick={(e) => {
e.stopPropagation();
this.unitDataOnDel(x, parentNode)
}}><Icon type="delete" /></a></span>}
isLeaf />
}
})
return arr;
}
解决要点:
如果我把请求后获取的数据作为 renderTree
的参数,势必造成state的改变。
这里需要用到一个hack,就是每次渲染都用深度拷贝的数据。深度拷贝的方法有很多种,也被用来出题。而最简单的:
let new_obj=JSON.parse(JSON.stringify(obj))
如果不考虑性能,这个操作也是逆天的。
按理来说,后端操作这个是最快的。前端只需要指定一个id即可。
结果后端设计结构时把他们设计为两个表了。删除变得异常复杂。因此需要前端告诉他树形节点的所有id。
因此需要更好的完善 renderTree
这里就用到了 findOne
方法。
核心方法如下:
getValue(obj, new_arr) {
try {
new_arr.push(obj.value)
} catch (error) {
console.log(new_arr)
}
if (obj.children) {
for (let i = 0; i < obj.children.length; i++) {
this.getValue(obj.children[i], new_arr)
}
return new_arr
}
}
与之前的相比,这个方法多了一个初始值为 []
的参数 new_arr
.作为递归的累加器(数组)。每次循环递归渲染,都会记录所有的节点id。
在删除方法中这么写:
let aaa = this.findOne(this.state.courseData, x.value);
aaa = this.getValue(aaa, [])
那么就得到了一个id列表。发给后端,他就可以愉快的删除了。
所有一系列问题的核心在于,后端采用了两个表来设计。树的结构有可能拥有一样的value。这是比较蛋疼的事情。
那么留作思考的问题来了:
应如何组织数据结构,才能很快的实现value值的不冲突呢?