图例
image.png

首先Diff为什么能优化性能

在渲染的过程中,
1.有可能会出现中间状态(不能直接到的最终状态),多次反复更新同一个元素(被操作元素)的情况
2.更新不需要被更新的元素
而Diff算法,就是为了减少这种无意义的消耗,而产生的一种算法。那Diff是不是没有缺点呢,也不是的,DIff通过虚拟元素节点来计算元素变更的情况,所以它会产生更多的内存消耗(因为要保存虚拟节点的状态),而且有可能因为计算会带来更多CPU的开销,所以掌握Diff的原理其实是很重要的一件事情。

在灵活使用Diff算法提高我们的渲染性能时需要注意哪些事情呢?

1.你的多次修改树的元素节点状态时,是否是当你确定修改完毕之后,在执行页面的重新计算与渲染

举一个例子,React中通过setState的方法更新虚拟DOM,并当“当前轮React事件”执行完毕后,开始重新计算虚拟DOM树,并按新的虚拟DOM更新页面。

  1. class Mod extends React.Component {
  2. state = {
  3. varA: 1,
  4. varB: 2,
  5. varC: 3
  6. }
  7. dispatchClick = () => {
  8. this.setState({varA: 'A'});
  9. this.setState({varB: 'B'});
  10. this.setState({varA: 'C'});
  11. }
  12. render () {
  13. cosnt {varA, varB, varC} = this.state;
  14. return (<div onClick={this.dispatchClick}>
  15. {varA},{varB},{varC}
  16. </div>)
  17. }
  18. }

上面的代码执行了3次setState,要求更新虚拟DOM状态。

假设多次变更没有合并会发送什么情况呢?

参考上面的图例,总共有12个子节点,每次更新时需要循环整个树,光循环次数就有36次了,有24次是其实是可以被合并的。

你可能会说,我当前这个组件没有子组件,它更新自身,应该是不需要循环整个树的,或者我有子组件,当我更新时,我应该循环的是某个子树,以减少循环次数的发送,理论上的确应该是这样的。

但是实际使用时,因为对象引用等情况,我是可以在某个节点中更新其他节点或其他子树让其发生变化的,也就是跨组件或者是跨树的更新的情况都有可能发生的,这是一个需要注意的问题。

2.当多个节点依赖或共享同一个状态时,此状态可能导致多个节点的无意义计算。

dva,redux是dispatch触发式数据流更新的状态管理库组件。
有的组件依赖某个状态可能是不涉及变更,有的组件依赖某个状态是涉及变更的,当这些组件混在一起时,有可能导致例外的开销。

React Diff

初始化阶段
1.首先必须要有一个原始树结构的数据源DATA,按此DATA生成对应的虚拟DOM树
2.循环此虚拟DOM树,将节点创建出来
3.将节点上下关系,串连起来,并渲染到页面中。
注意:React通过一组自定义事件,完成Proxy的功能,可以在事件执行前,与事件执行后,完成一系列的功能,所以它的setState是同步的,它通过Proxy在自定义事件触发时,标记状态,自定义事件结束后(此时状态在事件回调函数中合并),开始重新计算虚拟DOM,并完成更新。

更新
1.通过非React事件, 某个React实例调用setState更新
此时状态不合并
2.通过某个React实例的事件,发起setState更新
此时状态合并
3.通过某个React实例的事件,发起setState更新,但是在里面使用setTimeout,或某种导致异步发生的callback方法
此时状态不合并
4.直接通过React实例发起setState更新
此时状态不合并

具体的更新细节

虚拟DOM更新

假设有一个树R,树R中有a,b,c三个子树,以下三种情况
1.a子树发生更新,此时只循环a子树,跳过树R的根节点,与b,c子树
2.在a子树中某个节点,调用b子树的实例,驱动b子树的更新,跳过根节点,a,c子树 (跨组件的更新)
3.循环树R

层级遍历
在React中遍历树时,是按层序遍历的方式进行遍历的,用上面的那个图例来讲解
1.根节点
2.SubTreeNode1,SubTreeNode2,SubTreeNode3
3.SubTreeNode1.1,SubTreeNode1.2
按层级对比新老节点状态。
image.png

插入节点
删除节点
移动节点

Vue Diff