最近的需求里,需要绘制树形图,并且支持 新增/删除/转换 子节点,在操作时考虑到性能问题,有点感想,刚刚提交了今天的工作代码,来记录下思路。
要实现的效果图如下:
绘制树形图
因为近期工作中一直使用的echarts
库,所以自然而然想到了echarts
里的tree
实现。但是pm
原型图中连接线是直角折线,经调查,echarts4
都是曲线了,网上找到的方法说让修改源码。。。于是翻看配置得知可以搞成上图样儿的曲线,别说,其实我觉着还行,还好pm
也同意了哈哈。属性:curveness
series: {
data: currentData,
lineStyle: {
curveness: 1,
},
...defaultConfig,
},
数据结构如下,嵌套和嵌套和...:
{
"name": "发帖",
"type": 1,
"id": 1,
"children": [
{
"id": 2,
"prop": "yes",
"name": "分支2",
"type": 3,
"children": []
},
{
"id": 3,
"prop": "not",
"name": "文本长度>14",
"type": 1,
"children": [
{
"id": 4,
"prop": "not",
"name": "虚拟节点",
"type": 2,
"children": [
{
"name": "删除率>0.5",
"type": 1,
"id": 5,
"children": []
}
]
},
{
"id": 8,
"prop": "yes",
"name": "分支2",
"type": 3,
"features": "",
"children": []
}
]
}
]
}
功能呢,就如下图右键菜单的这些
为echarts
添加事件绑定
this.myChart.on('click', (param) => {
if (!param.seriesName) return;
const { data } = param;
});
this.myChart.on('contextmenu', (param) => {
if (!param.seriesName) return;
console.log(param); // 返回数据如下图
param.event.event.preventDefault(); // 阻止默认右键事件
const evt = param.event.event;
// 右键菜单定位需要
const { clientX, clientY} = evt;
this.options = {
x: clientX,
y: clientY,
};
this.menuVisible = true;
this.currData = param.data; // 当前右键目标node
});
性能问题
返回的节点信息如下图,我们所需的节点数据在属性data
里,现在问题来了,并不知道这个节点在总数据里的哪个位置,每次操作都循环一遍数据那我感觉废了,性能会降低没跑。
解决方案
我们的数据里,每个节点都有一个唯一标识符id
(并非数据库自增id
,是前端新增节点时的唯一序号),然后以node
的id+name
做key
,以node
的path
路径做value
存放。
例如 上面数据json
中id=4
的node path
是0.children.1.children.0
。接下来在处理数据时可以根据我们获取到的data
拼出key
,从而取出对应的路径,而不用每次操作node
都要去整体遍历数据。
下面以新增节点举例:转换和删除同理
const key = `${data.id}-${data.name}`;
const path = pathObj[key].split(',');
let currList = {}; // 取到nodes中对应的当前node数据
path.forEach((dot, index) => {
if (index === 0) {
currList = this.nodes[dot];
return;
}
currList = currList[dot];
});
// nodes存放时,使用了Object.freeze冻结了,vue不对做数据劫持,节省性能。
// 但freeze的是值,即nodes在栈中存放的对象地址,而对象的引用不受影响,仍然可以改变
this.nodes.push({
id: ++this.maxId, // 前端所寸唯一id,初始化时会取到现有数据里的最大id
name: '新节点',
type: 4,
children: [],
}, {
id: ++this.maxId,
name: '新节点',
type: 3,
children: [],
});
// 新增子节点的path也要保存在hash里,这里省略代码
this.renderEcharts(); // 更新
这样我们一次深度遍历取到所有的path,以后每次只需要循环path
精确取值,而不需要多次对整体数据做深度或广度遍历;
总结
虽然在项目中都有使用webpack
等进行打包和编译等的优化,但是在书写代码时我们 还是应该考虑性能问题,可能不注意的小问题都会引起最终的大问题,over!