react diff详细对比更新过程

327 阅读3分钟

React16之前版本的Diff算法

react的更新过程包括新旧虚拟DOM树的对比过程更新DOM过程

16版本之前,是对比、更新同时进行,对比的过程采用递归的方式,技术实现方式是不断的将各个节点、各个节点的子节点压入中,采用深度遍历的方式不断的访问子节点,回溯直到diff完整棵树。

整个过程由于是递归实现的,中间不能中断、中断后必须要重新开始,如果树的层级较深,会导致整个更新过程(js执行)时间过长,阻碍页面渲染和造成用户交互卡顿等问题,体验较差。

React16之后改进的Diff算法

由于递归算法、栈本身的局限性,16之后将递归改成迭代,并将栈结构改进成fiber链表结构,实现了更新过程可以随时中断的功能。

大概实现过程:

react将更新过程通过fiber时间分片的方式将整个更新任务分成多个小任务

会在一定时间内,将执行权交回给浏览器执行用户交互和渲染等操作

这样在用户角度上,不会有页面卡顿的情况出现,优化了用户体验

fiber时间分片看这里

React更新的两个阶段

16版本之后,将整个更新过程分为两个阶段:render阶段commit阶段

render阶段

diff过程:对比旧的fiber树和新的虚拟DOM树,生成新的fiber树的过程。此阶段可以中断

  • 第一轮遍历:
  1. 如果元素key和type都一样,表示可以复用旧DOM元素,利用旧fiber和新的元素的props生成新的fiber节点,并打标update
  2. 如果key一样,但是type不一样,表示不能复用,将旧fiber放在deletions数组中,利用新的虚拟DOM生成新的fiber,并打标placement(表示新建或者换位置)
  3. 如果key不一样,结束第一次遍历
  • 第二轮遍历
  1. 此时新旧树都存在没有对比过的元素,说明需要进行二次遍历

  2. 将旧的未比对过的元素key和元素value,放在map中,便于比对

  3. 取出新的虚拟DOM节点,在map中查找是否存在与自身key相等、type也一样的节点

    找到了:map中删除该节点,表示可复用

    没找到:打标placement,表示新增节点

  4. 结束后,还存在的map节点,全部放入deletions数组中

对比的过程中还存在顺序调整的问题,即a,b,c,d节点,更新成a,c,b,d,应该如何打标呢?

image.png

ME1675050101439.jpg

commit阶段

即更新DOM的过程,此过程不可中断。

  1. 删除deletions数组
  2. 如果该元素有Placement标识,将该元素移动到没有标识的元素的后面
  3. 更新节点,表示为update的节点,根据props更新节点