一个
DOM节点在某一时刻最多会有4个节点和他相关
current Fiber:页面上DOM节点对应的Fiber节点workInProgress Fiber:本次更新中DOM节点对应的Fiber节点DOM节点本身JSX对象:函数组件或类组件的调用结果
Diff算法的本质是对比 1 和 4 生成 2 的过程
1、整体流程
React的diff三个策略
- 只对同级元素进行
Diff- 两个不同类型元素会产生出不同的树
- 如果元素由
div变为p,- 销毁
div及其子孙节点,并新建p及其子孙节点- 通过
key 属性来维护哪些子元素在不同的渲染下能保持稳定
2、Diff分为两类
- 单节点:
newChild类型为object、number、string - 多节点:
newChild类型为Array - 特点:整个diff过程,就是 currentFirstChild 和 element 做对比,返回
新的fiber的过程
单节点diff
key相同
type相同时,节点可复用type不同时,将child及sibing的fiber都标记删除 (不能被复用)
key不同
- 仅将
child的fiber标记删除(交换位置时可能被复用)
多节点diff
第一轮遍历:处理
更新的节点
- 遍历
newChildren,将newChildren[i]与oldFiber比较- 三种情况:
- key相同type相同:复用
- key相同type不同:oldFiber标记删除、继续遍历
- key不同:
直接跳出,可能属于交换位置第二轮遍历:处理剩下的不属于
更新的节点
newChildren与oldFiber同时遍历完 --> 结束newChildren没遍历完 --> 新增oldFiber没遍历完 --> 删除newChildren与oldFiber都没遍历完(交换位置)
- 由于有节点改变了位置,所以不能再用位置索引
i对比前后的节点- 将所有还未处理的
oldFiber存入以key为key,oldFiber为value的Map中- 遍历剩余的
newChildren,通过newChildren[i].key就能在existingChildren中找到key相同的oldFiber
3、案例
比如父节点下有 A、B、C、D 四个子节点,那渲染出的 vdom 就是这样的:
经过 reconcile 之后,会变成这样的 fiber 结构:
那如果再次渲染的时候,渲染出了 A、C、B、E 的 vdom,这时候怎么处理呢?
再次渲染出 vdom 的时候,也要进行 vdom 转 fiber 的 reconcile 阶段,但是要尽量能复用之前的节点。 那怎么复用呢?一一对比下不就行了?先把之前的 fiber 节点放到一个 map 里,key 就是节点的 key:
然后每个新的 vdom 都去这个 map 里查找下有没有可以复用的,找到了的话就移动过来,打上更新的 effectTag:
这样遍历完 vdom 节点之后,map 里剩下一些,这些是不可复用的,那就删掉,打上删除的 effectTag;如果 vdom 中还有一些没找到复用节点的,就直接创建,打上新增的 effectTag。
这样就实现了更新时的 reconcile,也就是上面的 diff 算法。其实核心就是找到可复用的节点,剩下的旧节点删掉,新节点新增。这样就完成了新的 fiber 结构的创建,也就是 reconcile 的过程。
比如上面那个例子,第一轮遍历就是这样的:
一一对比新的 vdom 和 老的 fiber,发现 A 是可以复用的,那就创建新 fiber 节点,打上更新标记。
C 不可复用,所以结束第一轮遍历,进入第二轮遍历。
把剩下的 老 fiber 节点放到 map 里,然后遍历新的 vdom 节点,从 map 中能找到的话,就是可复用,移动过来打上更新的标记。
遍历完之后,剩下的老 fiber 节点删掉,剩下的新 vdom 新增。
这样就完成了更新时的 reconcile 的过程。