react对diff做的优化
- 只对同级元素进行diff。
- 两个不同类型的元素产生不同的树。
- 可以通过key暗示哪些子元素在不同的渲染下保持稳定。
diff的实现
- diff分为单节点diff,多节点diff
- 当newChild类型为object、number、string、代表同级只有一个节点
- 当newChild类型为array,同级有多个节点
- 前后diff的是谁
oldFiber和新的jsx转成的vdom
// 根据newChild类型选择不同diff函数处理
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
): Fiber | null {
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {// 单节点diff
// object类型,可能是 REACT_ELEMENT_TYPE 或 REACT_PORTAL_TYPE
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
// 调用 reconcileSingleElement 处理
// // ...省略其他case
}
}
if (typeof newChild === 'string' || typeof newChild === 'number') {
// 调用 reconcileSingleTextNode 处理
// ...省略
}
if (isArray(newChild)) {// 多节点diff
// 调用 reconcileChildrenArray 处理
// ...省略
}
// 一些其他情况调用处理函数
// ...省略
// 以上都没有命中,删除节点
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
单节点diff(newfiber是单个)的处理方式比较过程
复用逻辑
先判断key是否相同,如果key相同则判断type是否相同,只有都相同时一个dom节点才能复用。
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement
): Fiber {
const key = element.key;
let child = currentFirstChild;
// 首先判断是否存在对应DOM节点
while (child !== null) {
// 上一次更新存在DOM节点,接下来判断是否可复用
// 首先比较key是否相同
if (child.key === key) {
// key相同,接下来比较type是否相同
switch (child.tag) {
// ...省略case
default: {
if (child.elementType === element.type) {
// type相同则表示可以复用
// 返回复用的fiber
return existing;
}
// type不同则跳出switch
break;
}
}
// 代码执行到这里代表:key相同但是type不同
// 将该fiber及其兄弟fiber标记为删除
deleteRemainingChildren(returnFiber, child);
break;
} else {
// key不同,将该fiber标记为删除
deleteChild(returnFiber, child);
}
child = child.sibling;
}
// 创建新Fiber,并返回 ...省略
}
多节点diff的处理方式
不能用数组双指针的方式遍历,因为oldFiber是链表 ,vdom是数组
所以需要进行两次遍历
-
第一轮遍历 处理更新的节点
- 遍历newChildren,将newChildren[i]与oldFiber比较,判断dom节点是否可复用。
- 如果可以复用,i++,继续比较newChildren[]与oldFiber.sibling
- 如果不可以复用,key不同导致的不可复用,跳出循环第一轮遍历结束(都没遍历完),type不同导致的不可复用,将oldFiber标记为DELETION,并继续遍历。
- 如果newChildren遍历完或者oldFiber遍历完,跳出循环,第一轮遍历结束。
-
第二轮遍历 处理剩下不属于更新的节点
-
newChildren与oldFiber同时遍历完
只需在第一轮遍历进行组件更新,diff结束,所有组件都可以复用 -
newChildren没遍历完,oldFiber遍历完
本次更新有新节点插入,需要遍历剩下的newChildren为生成workInProgress fiber 依次标记placement -
newChildren遍历完,oldFiber没遍历完
有节点被删除,将剩下的oldFiber,依次标记Deletion。 -
newChildren与oldFiber都没遍历完
位置改变,需要处理移动的节点,oldFiber的节点根据lastPlaceIndex向后移动
abcd变为dabc 需要将abc向后移动 abcd变为acdb 需要将b向后移动 -











网友评论