React16之前版本的Diff算法
react的更新过程包括新旧虚拟DOM树的对比过程
和更新DOM过程
。
16版本之前,是对比、更新
同时进行,对比的过程采用递归
的方式,技术实现方式是不断的将各个节点、各个节点的子节点压入栈
中,采用深度遍历的方式不断的访问子节点,回溯直到diff完整棵树。
整个过程由于是递归
实现的,中间不能中断、中断后必须要重新开始,如果树的层级较深,会导致整个更新过程(js执行)时间过长,阻碍页面渲染和造成用户交互卡顿
等问题,体验较差。
React16之后改进的Diff算法
由于递归算法、栈本身的局限性
,16之后将递归改成迭代
,并将栈结构改进成fiber链表结构
,实现了更新过程可以随时中断的功能。
大概实现过程:
react将更新过程通过fiber时间分片的方式将整个更新任务分成多个小任务
会在一定时间内,将执行权交回给浏览器执行用户交互和渲染等操作
这样在用户角度上,不会有页面卡顿的情况出现,优化了用户体验
React更新的两个阶段
16版本之后,将整个更新过程分为两个阶段:render阶段
和commit阶段
render阶段
diff过程:对比旧的fiber树和新的虚拟DOM树,生成新的fiber树的过程。此阶段可以中断
- 第一轮遍历:
- 如果
元素key和type都一样
,表示可以复用旧DOM元素,利用旧fiber和新的元素的props生成新的fiber节点,并打标update
- 如果
key一样,但是type不一样
,表示不能复用,将旧fiber放在deletions数组中,利用新的虚拟DOM生成新的fiber,并打标placement
(表示新建或者换位置) - 如果key不一样,结束第一次遍历
- 第二轮遍历
-
此时新旧树都存在没有对比过的元素,说明需要进行二次遍历
-
将旧的未比对过的元素key和元素value,放在map中,便于比对
-
取出新的虚拟DOM节点,在map中查找是否存在与自身key相等、type也一样的节点
找到了
:map中删除该节点,表示可复用没找到
:打标placement,表示新增节点 -
结束后,还存在的map节点,全部放入deletions数组中
对比的过程中还存在顺序调整
的问题,即a,b,c,d节点,更新成a,c,b,d
,应该如何打标呢?
commit阶段
即更新DOM的过程,此过程不可中断。
- 删除deletions数组
- 如果该元素有Placement标识,将该元素移动到没有标识的元素的后面
- 更新节点,表示为update的节点,根据props更新节点