工作中问题:树形结构修改内部数据性能考虑

660 阅读3分钟

最近的需求里,需要绘制树形图,并且支持 新增/删除/转换 子节点,在操作时考虑到性能问题,有点感想,刚刚提交了今天的工作代码,来记录下思路。

要实现的效果图如下:

绘制树形图

因为近期工作中一直使用的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,是前端新增节点时的唯一序号),然后以nodeid+namekey,以nodepath路径做value存放。

例如 上面数据jsonid=4node path0.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!

有问题望指正,感谢!