buuing/vue-search-tree

关于首次渲染卡顿以及watch监听被反复触发的性能优化

Opened this issue · 0 comments

第一版: 深度监听data.children

因为考虑到树是从上到下逐级生成的, 如果使用v-if让子节点不渲染, 那子节点的watch和computed也就不会创建, 所以刚一开始的时候, 我的思路是使用watch深度监听data.children, 外加v-show使得子节点在初始阶段只渲染一次, 后续的展开折叠也属于高频操作, 也有利于节省性能

但是这种情况到了应用时, 才发现如果监听的是data.children, watch也会被该节点的无用属性触发, 比如展开折叠改变的expand属性也属于data的一部分, 也就是说树插件的每一次操作都会触发一堆深度监听的watch, 即便这些操作是与checked不相关的, 还有就是由于使用v-show, 首次渲染时要创建所有节点, 测试4000+单节点的首次渲染时间高达4.5s左右, 导致js程持续占用GUI渲染线程


第二版: 单独监听data.checked外加$emit向上传递变化

之后仔细研究element的tree源码之后, 我发现他是单独监听的data.checked, 然后通过$emit逐级向上触发一个函数, 函数内通过js向下探知子节点的选中状态来改变自身, 这样做的好处是, 不再需要担心节点其他无用属性的干扰触发, 但是当我再次应用后发现, watch被触发的频率会随着节点的深度增加, 会裂变非常恐怖

比如树结构有6层节点, 现在我让其中一个节点选中, 当前节点观察到变化向上触发一次$emit, 然后下面的节点也会被全部被选中, 同样触发一次子节点的watch, 然后又一次的向上$emit, 就像是水波纹一样触底之后逐渐扩大, 尤其是节点增加到4000+的时候, 点击一次checked的延迟非常高

其实这个问题在第一版监听children的时候也存在, 因为是深度监听, 所以也就是触发的顺序略有不同罢了, 下图为深度监听的触发

20200702103457


第三版: 保留$emit向上传递, 弃用watch监听, 改用原生js通过前序和层序向下遍历

问题就是处在watch在监听时会重复触发$emit, 那么解决方案也只能是放弃掉watch, 每一次的checked改变分为两步走, 一是继续通过$emit向上传递事件, 二是使用原生js向下提前感知节点变化, 通过递归每次只改变当前节点, 前序遍历半选状态, 层序遍历叶子节点状态, 得到结果提前打断函数来节省性能

好处是子节点不再新建watch, 可以放心的使用v-if优化首次渲染问题, 把海量节点的渲染分摊到用户的每次点击操作, 目前测试4000+的单一树节点也运行流畅, 缺点是以后所有封装改变checked的方法都严重依赖原生js


最终和 el-tree 的性能对比

computed-tree