对于单个节点,我们以类型object为例,会进入reconcileSingleElement
你可以从这里
(opens new window)看到
reconcileSingleElement源码
const isObject = typeof newChild === 'object' && newChild !== null;if (isObject) {// 对象类型,可能是 REACT_ELEMENT_TYPE 或 REACT_PORTAL_TYPEswitch (newChild.$$typeof) {case REACT_ELEMENT_TYPE:// 调用 reconcileSingleElement 处理// ...其他case}}
这个函数会做如下事情:

让我们看看第二步判断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) {// ...省略casedefault: {if (child.elementType === element.type) {// type相同则表示可以复用// 返回复用的fiberreturn existing;}// type不同则跳出switchbreak;}}// 代码执行到这里代表: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 !== null且key相同且type不同时执行deleteRemainingChildren将child及其兄弟fiber都标记删除。当
child !== null且key不同时仅将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,但是p与li 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,但是更新前type为div,更新后为p,type改变则不能复用。
习题2: 更新前后key改变,不需要再判断type,不能复用。
习题3: 更新前后key改变,不需要再判断type,不能复用。
习题4: 更新前后key与type都未改变,可以复用。children变化,DOM的子元素需要更新。
你是不是都答对了呢。
