美文网首页
react17源码解读-diff

react17源码解读-diff

作者: Mr无愧于心 | 来源:发表于2022-02-23 09:57 被阅读0次

react对diff做的优化

  1. 只对同级元素进行diff。
  2. 两个不同类型的元素产生不同的树。
  3. 可以通过key暗示哪些子元素在不同的渲染下保持稳定。
diff的实现
  • diff分为单节点diff,多节点diff
    1. 当newChild类型为object、number、string、代表同级只有一个节点
    2. 当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是数组
所以需要进行两次遍历

  • 第一轮遍历 处理更新的节点

    1. 遍历newChildren,将newChildren[i]与oldFiber比较,判断dom节点是否可复用。
    2. 如果可以复用,i++,继续比较newChildren[]与oldFiber.sibling
    3. 如果不可以复用,key不同导致的不可复用,跳出循环第一轮遍历结束(都没遍历完),type不同导致的不可复用,将oldFiber标记为DELETION,并继续遍历。
    4. 如果newChildren遍历完或者oldFiber遍历完,跳出循环,第一轮遍历结束
  • 第二轮遍历 处理剩下不属于更新的节点

    1. newChildren与oldFiber同时遍历完
      只需在第一轮遍历进行组件更新,diff结束,所有组件都可以复用

    2. newChildren没遍历完,oldFiber遍历完
      本次更新有新节点插入,需要遍历剩下的newChildren为生成workInProgress fiber 依次标记placement

    3. newChildren遍历完,oldFiber没遍历完
      有节点被删除,将剩下的oldFiber,依次标记Deletion。

    4. newChildren与oldFiber都没遍历完
      位置改变,需要处理移动的节点,oldFiber的节点根据lastPlaceIndex向后移动

    abcd变为dabc 需要将abc向后移动
    abcd变为acdb 需要将b向后移动
    

相关文章

网友评论

      本文标题:react17源码解读-diff

      本文链接:https://www.haomeiwen.com/subject/bblncrtx.html