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

你可以从这里单节点Diff - 图1 (opens new window)看到reconcileSingleElement源码

  1. const isObject = typeof newChild === 'object' && newChild !== null;
  2. if (isObject) {
  3. // 对象类型,可能是 REACT_ELEMENT_TYPE 或 REACT_PORTAL_TYPE
  4. switch (newChild.$$typeof) {
  5. case REACT_ELEMENT_TYPE:
  6. // 调用 reconcileSingleElement 处理
  7. // ...其他case
  8. }
  9. }

这个函数会做如下事情:

diff

让我们看看第二步判断DOM节点是否可以复用是如何实现的。

  1. function reconcileSingleElement(
  2. returnFiber: Fiber,
  3. currentFirstChild: Fiber | null,
  4. element: ReactElement
  5. ): Fiber {
  6. const key = element.key;
  7. let child = currentFirstChild;
  8. // 首先判断是否存在对应DOM节点
  9. while (child !== null) {
  10. // 上一次更新存在DOM节点,接下来判断是否可复用
  11. // 首先比较key是否相同
  12. if (child.key === key) {
  13. // key相同,接下来比较type是否相同
  14. switch (child.tag) {
  15. // ...省略case
  16. default: {
  17. if (child.elementType === element.type) {
  18. // type相同则表示可以复用
  19. // 返回复用的fiber
  20. return existing;
  21. }
  22. // type不同则跳出switch
  23. break;
  24. }
  25. }
  26. // 代码执行到这里代表:key相同但是type不同
  27. // 将该fiber及其兄弟fiber标记为删除
  28. deleteRemainingChildren(returnFiber, child);
  29. break;
  30. } else {
  31. // key不同,将该fiber标记为删除
  32. deleteChild(returnFiber, child);
  33. }
  34. child = child.sibling;
  35. }
  36. // 创建新Fiber,并返回 ...省略
  37. }

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

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

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

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

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

考虑如下例子:

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

  1. // 当前页面显示的
  2. ul > li * 3
  3. // 这次需要更新的
  4. 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. // 习题1 更新前
  2. <div>ka song</div>
  3. // 更新后
  4. <p>ka song</p>
  5. // 习题2 更新前
  6. <div key="xxx">ka song</div>
  7. // 更新后
  8. <div key="ooo">ka song</div>
  9. // 习题3 更新前
  10. <div key="xxx">ka song</div>
  11. // 更新后
  12. <p key="ooo">ka song</p>
  13. // 习题4 更新前
  14. <div key="xxx">ka song</div>
  15. // 更新后
  16. <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的子元素需要更新。

你是不是都答对了呢。