# 单个节点

对于单个节点,我们以类型object为例,会进入reconcileSingleElement

你可以从这里 (opens new window)看到reconcileSingleElement源码

const isObject = typeof newChild === "object" && newChild !== null;

if (isObject) {
  // 对象类型,可能是 REACT_ELEMENT_TYPE 或 REACT_PORTAL_TYPE
  switch (newChild.$$typeof) {
    case REACT_ELEMENT_TYPE:
    // 调用 reconcileSingleElement 处理
    // ...其他case
  }
}

这个函数会做如下事情:

diff

让我们看看第二步判断 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,并返回 ...省略
}

还记得我们刚才提到的,React 预设的限制么,

从代码可以看出,React 通过先判断key是否相同,如果key相同则判断type是否相同,只有都相同时一个DOM节点才能复用。

这里有个细节需要关注下:

  • child !== nullkey相同type不同时执行deleteRemainingChildrenchild及其兄弟fiber都标记删除。

  • child !== nullkey不同时仅将child标记删除。

考虑如下例子:

当前页面有 3 个li,我们要全部删除,再插入一个p

// 当前页面显示的
ul > li * 3;

// 这次需要更新的
ul > p;

由于本次更新时只有一个p,属于单一节点的Diff,会走上面介绍的代码逻辑。

reconcileSingleElement中遍历之前的 3 个fiber(对应的DOM为 3 个li),寻找本次更新的p是否可以复用之前的 3 个fiber中某个的DOM

key相同type不同时,代表我们已经找到本次更新的p对应的上次的fiber,但是pli type不同,不能复用。既然唯一的可能性已经不能复用,则剩下的fiber都没有机会了,所以都需要标记删除。

key不同时只代表遍历到的该fiber不能被p复用,后面还有兄弟fiber还没有遍历到。所以仅仅标记该fiber删除。

# 练习题

让我们来做几道习题巩固下吧:

请判断如下JSX对象对应的DOM元素是否可以复用:

// 习题1 更新前
<div>ka song</div>
// 更新后
<p>ka song</p>

// 习题2 更新前
<div key="xxx">ka song</div>
// 更新后
<div key="ooo">ka song</div>

// 习题3 更新前
<div key="xxx">ka song</div>
// 更新后
<p key="ooo">ka song</p>

// 习题4 更新前
<div key="xxx">ka song</div>
// 更新后
<div key="xxx">xiao bei</div>

公布答案:

习题 1: 未设置key prop默认 key = null;,所以更新前后 key 相同,都为null,但是更新前typediv,更新后为ptype改变则不能复用。

习题 2: 更新前后key改变,不需要再判断type,不能复用。

习题 3: 更新前后key改变,不需要再判断type,不能复用。

习题 4: 更新前后keytype都未改变,可以复用。children变化,DOM的子元素需要更新。

你是不是都答对了呢。

上次更新: 11/8/2024, 10:19:43 AM