ZMindMap
ZMindMap copied to clipboard
代码优化之路
记录项目重构优化过程中的重大调整。
【问题】 store中直接发起http请求, 使store看起来很混乱。 【分析】 所有store的职责只有对状态的维护,除此之外不应该做任何事。 【解决】 将所有http请求抽取到统一的工具文件,根据不同功能模块暴露相关方法。store只是调用相关方法。 see: https://github.com/zyascend/ZMindMap/commit/b2a94c9fb2f47165591033357a7eb39874a2fa7f
【问题】 导图相关尺寸位置计算依赖于store中保存的svg的ref。相关问题:https://github.com/zyascend/ZMindMap/issues/4 【分析】 纯计算逻辑不应该依赖于其他额外状态,只应该跟输入的数据源有关。需要解耦。 【解决】 store中不再保存dom的ref,在计算逻辑中自己获取dom参与计算。 see: https://github.com/zyascend/ZMindMap/commit/c3abf872c6388e8f8d9faa67b3d472067f3dcca8
【问题】 缩放导图的逻辑在setup中四处分散,遍布于各个生命周期,不优雅,难维护。 【分析】 应该根据CompositionApi的思想将逻辑集中起来,封装成一个功能单一的hooks。 【解决】
import useAutoZoom from '@/hooks/map/useAutoZoom'
useAutoZoom(renderData) // 封装后 一行代码解决!
see: https://github.com/zyascend/ZMindMap/commit/484a61804e0c95dfb19c5a8888cb99ced8a11490
【问题】
不同风格的导图的计算逻辑存在公共部分,这些公共部分不应分散于各个计算逻辑中,后期不便维护且代码冗余。
【分析】
抽取公共计算逻辑到一个父类Tree
中,所有风格的计算逻辑继承自该父类
【解决】
see: https://github.com/zyascend/ZMindMap/commit/7fb9a8f2f585b3ab9253e13b29e07a900beba7e9
【问题】 导出导图为PNG图片的功能有大问题。 表现为: 1. 【错误处理】一旦导出过程中出错,await的方式没catch error 导致loading无法关闭。 2. 【图片质量】导出的图片大小会受到浏览器窗口大小的影响 而不是导图的原始大小。
![]() 浏览器视窗状态 |
![]() 优化前导出效果 |
![]() 优化后导出效果 |
-
【错误处理】需要封装为Promise即可catch error
-
【图片质量】计算导图原始尺寸 --> 重设svg尺寸 --> 将内容移到中间 --> 转换为png
see: https://github.com/zyascend/ZMindMap/commit/720465396a5657e21a706d97c23c3a2396fbd784
【问题】 扫码登录, 获取二维码的状态的方式是轮询。
【分析】
轮询的缺点
- 浪费带宽
- 消耗服务器CPU占用
- 数据更新不及时
【解决】 see: https://github.com/zyascend/ZMindMap/commit/aeb70a56e4787546bd88abe00febfb00b1c34383 https://github.com/zyascend/mind-map-node/commit/ae7521dc1a0cbc2fd9648f21b86d6a6f17ca89cf
【数据结构的转换与遍历相关优化】
原则: 不递归!!!
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遍历?
当导图节点数达到1000个会发生什么?
查看导出的图片:https://pic.imgdb.cn/item/62dabe6cf54cd3f937ea4b5b.png
1. 数据初始化:接口返回的数据大小达900多k
- [ ] 想办法压缩JSON
比如:需要往节点添加新的子节点 post的data可以定义为如下格式。即post“操作本身”,而不必将整个导图数据传回后台。
2. 数据更新:需要post的请求体超级大,每一个小改动都把整个导图序列化数据传回去???代价巨大。
实测导致问题:
- [ ] 需要重写更新数据接口,简化post的请求体
const data = {
docId: 'd34124dsa2313da',
type: 'append', // append = 添加子节点 delete = 删除子节点
currentNode: 'd34dd',
parentNode: '123ew'
}
3. 节点计算:当1个节点变化,其余所有节点都要重新计算一遍么???
测量一下计算耗时
100个节点
500个节点
1000个节点
结论
耗时部分主要集中在 计算节点宽高 为什么? 节点宽高计算逻辑包括:文字宽高计算 + 图片宽高计算 + 标记宽高计算 + 节点总宽高计算 无疑,文字宽高计算最耗时 因为其内部逻辑有
- 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 // 子节点被删除