ZMindMap icon indicating copy to clipboard operation
ZMindMap copied to clipboard

代码优化之路

Open zyascend opened this issue 2 years ago • 8 comments

记录项目重构优化过程中的重大调整。

zyascend avatar Jul 12 '22 07:07 zyascend

【问题】 store中直接发起http请求, 使store看起来很混乱。 【分析】 所有store的职责只有对状态的维护,除此之外不应该做任何事。 【解决】 将所有http请求抽取到统一的工具文件,根据不同功能模块暴露相关方法。store只是调用相关方法。 see: https://github.com/zyascend/ZMindMap/commit/b2a94c9fb2f47165591033357a7eb39874a2fa7f

zyascend avatar Jul 12 '22 07:07 zyascend

【问题】 导图相关尺寸位置计算依赖于store中保存的svg的ref。相关问题:https://github.com/zyascend/ZMindMap/issues/4 【分析】 纯计算逻辑不应该依赖于其他额外状态,只应该跟输入的数据源有关。需要解耦。 【解决】 store中不再保存dom的ref,在计算逻辑中自己获取dom参与计算。 see: https://github.com/zyascend/ZMindMap/commit/c3abf872c6388e8f8d9faa67b3d472067f3dcca8

zyascend avatar Jul 12 '22 07:07 zyascend

【问题】 缩放导图的逻辑在setup中四处分散,遍布于各个生命周期,不优雅,难维护。 【分析】 应该根据CompositionApi的思想将逻辑集中起来,封装成一个功能单一的hooks。 【解决】

import useAutoZoom from '@/hooks/map/useAutoZoom'
useAutoZoom(renderData) // 封装后 一行代码解决!

see: https://github.com/zyascend/ZMindMap/commit/484a61804e0c95dfb19c5a8888cb99ced8a11490

zyascend avatar Jul 12 '22 08:07 zyascend

【问题】 不同风格的导图的计算逻辑存在公共部分,这些公共部分不应分散于各个计算逻辑中,后期不便维护且代码冗余。 【分析】 抽取公共计算逻辑到一个父类Tree中,所有风格的计算逻辑继承自该父类 【解决】 see: https://github.com/zyascend/ZMindMap/commit/7fb9a8f2f585b3ab9253e13b29e07a900beba7e9

zyascend avatar Jul 12 '22 16:07 zyascend

【问题】 导出导图为PNG图片的功能有大问题。 表现为: 1. 【错误处理】一旦导出过程中出错,await的方式没catch error 导致loading无法关闭。 2. 【图片质量】导出的图片大小会受到浏览器窗口大小的影响 而不是导图的原始大小。


浏览器视窗状态

优化前导出效果

优化后导出效果
【分析 & 解决】
  1. 【错误处理】需要封装为Promise即可catch error

  2. 【图片质量】计算导图原始尺寸 --> 重设svg尺寸 --> 将内容移到中间 --> 转换为png

see: https://github.com/zyascend/ZMindMap/commit/720465396a5657e21a706d97c23c3a2396fbd784

zyascend avatar Jul 13 '22 16:07 zyascend

【问题】 扫码登录, 获取二维码的状态的方式是轮询。

【分析】

轮询的缺点

  1. 浪费带宽
  2. 消耗服务器CPU占用
  3. 数据更新不及时

【解决】 see: https://github.com/zyascend/ZMindMap/commit/aeb70a56e4787546bd88abe00febfb00b1c34383 https://github.com/zyascend/mind-map-node/commit/ae7521dc1a0cbc2fd9648f21b86d6a6f17ca89cf

zyascend avatar Jul 15 '22 16:07 zyascend

【数据结构的转换与遍历相关优化】

原则: 不递归!!!

1. 扁平结构转树形结构

function flatToTree(data) {
  if (!data) return null
  const values = Object.values(data)
  const treeData = values.filter(item => {
    const { _children, id } = item
    if (_children.length) {
      item._children = values.filter(e => {
        return id === e.parent
      })
    } else {
      item.children = values.filter(e => {
        return id === e.parent
      })
    }
    return item.parent === '-1'
  })
  return treeData[0]
}

2. 获取多叉树的先序遍历序列

/**
 * 获取N叉树的先序序列 = 大纲编辑页的展示顺序
 * @param {*} root
 * @returns
 */
function cyclePreOrder(root) {
  const stack = []
  const res = []
  root.level = 0
  stack.push(root)
  while (stack.length) {
    const cur = stack.pop()
    res.push(cur)
    const len = cur?.children?.length
    if (len) {
      cur.children.forEach((v, i) => {
        // ! 倒序入栈才能顺序出栈
        const child = cur.children[len - 1 - i]
        child.level = cur.level + 1
        stack.push(child)
      })
    }
  }
  return res
}

see: https://github.com/zyascend/ZMindMap/commit/aeffa94d739d960e61b9f807366feed126405c33

----------update------------ Morris遍历?

zyascend avatar Jul 20 '22 15:07 zyascend

当导图节点数达到1000个会发生什么?

查看导出的图片:https://pic.imgdb.cn/item/62dabe6cf54cd3f937ea4b5b.png

1. 数据初始化:接口返回的数据大小达900多k

  • [ ] 想办法压缩JSON

比如:需要往节点添加新的子节点 post的data可以定义为如下格式。即post“操作本身”,而不必将整个导图数据传回后台。

2. 数据更新:需要post的请求体超级大,每一个小改动都把整个导图序列化数据传回去???代价巨大。

实测导致问题: image

  • [ ] 需要重写更新数据接口,简化post的请求体
const data = {
  docId: 'd34124dsa2313da',
  type: 'append', // append = 添加子节点  delete = 删除子节点
  currentNode: 'd34dd',
  parentNode: '123ew'
}

3. 节点计算:当1个节点变化,其余所有节点都要重新计算一遍么???

测量一下计算耗时

100个节点 image

500个节点 image

1000个节点 image

结论

耗时部分主要集中在 计算节点宽高 为什么? 节点宽高计算逻辑包括:文字宽高计算 + 图片宽高计算 + 标记宽高计算 + 节点总宽高计算 无疑,文字宽高计算最耗时 因为其内部逻辑有

  • for循环。
  • dom节点的插入删除。

其他计算都是简单计算。

如何优化

耗时部分(文字宽高)优化

1.只进行一次dom计算,算出单个字符所占的宽度,以此为宽度基准 2. 得到宽度基准之后,对于每个节点不必再次插入dom。 3. 宽度基准 * 节点文字长度 = 文本宽度 4. 文本宽度 / 行高 = 文本高度(占几行)

注意点:

  • 选取最宽的字符(W,M, 齉)组成一串来计算宽度基准
  • 宽度基准受字体、字号、style的影响。

节点尺寸方面:

只有节点本身和该节点的所有祖先需要重新计算大小(宽高),而且祖先只需要重新计算外围宽高,其自身内部的文本、图片、标记等元素是不需要重新计算的。

节点位置方面:

横向位置:该节点及其所有子代需要更新。 纵向位置:树的后序序列中,位于该节点后面的所有节点都需更新。

优化方案:

参考Vue3关于编译优化中的patchFlags,对需要更新的节点打标记。根据标记情况决定节点是否需要重新计算。

PATCH_FLAG = 1 // 宽高变化
PATCH_FLAG = 2 // X值变化
PATCH_FLAG = 3 // Y值变化
PATCH_FLAG = 4 // 新增的节点
PATCH_FLAG = 3 // 子节点被删除

zyascend avatar Jul 22 '22 15:07 zyascend