commitMutationEffects
commitMutationEffects是一个while循环,遍历包含effect tag的fiber节点的链表。
遍历的每一个Fiber节点:
- 会判断是否包含ContentReset,即是否需要重置文本节点。
- 是否包含Ref

- 判断是否包含Placement(插入dom)、Update(更新)、Delection(删除)、Hydrating(ssr)相关
- Placement,执行commitPlacement(nextEffect)
- 当前环境不支持mutation,则直接返回。
placement
commitPlacement
function commitPlacement(finishedWork) {var parentFiber = getHostParentFiber(finishedWork); // Note: these two variables *must* always be updated together.switch (parentFiber.tag) {case HostComponent:{var parent = parentFiber.stateNode;if (parentFiber.flags & ContentReset) {// Reset the text content of the parent before doing any insertionsresetTextContent(parent); // Clear ContentReset from the effect tagparentFiber.flags &= ~ContentReset;}var before = getHostSibling(finishedWork); // We only have the top Fiber that was inserted but we need to recurse down its// children to find all the terminal nodes.insertOrAppendPlacementNode(finishedWork, before, parent);break;}case HostRoot:case HostPortal:{var _parent = parentFiber.stateNode.containerInfo;var _before = getHostSibling(finishedWork);insertOrAppendPlacementNodeIntoContainer(finishedWork, _before, _parent);break;}// eslint-disable-next-line-no-fallthroughdefault:throw new Error('Invalid host parent fiber. This error is likely caused by a bug ' + 'in React. Please file an issue.');}}
根据当前fiber节点,找到离他最近的Host Fiber节点,包括HostComponent、HostRoot、HostPortal(通过React.createHostPorta传进来的),这几种类型共同点:都对应有dom节点。
当找到parentFiber时,如果parentFiber包含ContentReset的Effect tag,就需要重置resetTextContent,ContentReset Effect tag并取反(即删除这个effctTag)。
接下来,找getHostSibling,当前Fiber类型的兄弟节点。
为什么要找兄弟节点?
插入一个dom节点有两种方式:一种是sibling.insertBefore方法,另一个时parentNode.append(child)。因此如果使用insertBefore就需要寻找兄弟节点,如果是append,则需要parentNode节点。
function insertOrAppendPlacementNode(node, before, parent) {var tag = node.tag;var isHost = tag === HostComponent || tag === HostText;if (isHost) {var stateNode = node.stateNode;//如果当前节点的兄弟节点存在,就执行insertBefore,否则appendChildif (before) {insertBefore(parent, stateNode, before);} else {appendChild(parent, stateNode);}} else if (tag === HostPortal) ; else {var child = node.child;if (child !== null) {insertOrAppendPlacementNode(child, before, parent);var sibling = child.sibling;while (sibling !== null) {insertOrAppendPlacementNode(sibling, before, parent);sibling = sibling.sibling;}}}}
//调用Dom的insertBefore方法function insertBefore(parentInstance, child, beforeChild) {parentInstance.insertBefore(child, beforeChild);}function insertInContainerBefore(container, child, beforeChild) {if (container.nodeType === COMMENT_NODE) {container.parentNode.insertBefore(child, beforeChild);} else {container.insertBefore(child, beforeChild);}}//调用Dom的appendChild方法function appendChild(parentInstance, child) {parentInstance.appendChild(child);}
getHostParentFiber
getHostParentFiber:一直向上递归查找最近的host类型Fiber节点,知道找到为止。
function getHostParentFiber(fiber) {var parent = fiber.return;while (parent !== null) {if (isHostParent(parent)) {return parent;}parent = parent.return;}throw new Error('Expected to find a host parent. This error is likely caused by a bug ' + 'in React. Please file an issue.');}
getHostSibling
function getHostSibling(fiber) {// We're going to search forward into the tree until we find a sibling host// node. Unfortunately, if multiple insertions are done in a row we have to// search past them. This leads to exponential search for the next sibling.// TODO: Find a more efficient way to do this.var node = fiber;siblings: while (true) {// If we didn't find anything, let's try the next sibling.while (node.sibling === null) {if (node.return === null || isHostParent(node.return)) {// If we pop out of the root or hit the parent the fiber we are the// last sibling.return null;}node = node.return;}node.sibling.return = node.return;node = node.sibling;while (node.tag !== HostComponent && node.tag !== HostText && node.tag !== DehydratedFragment) {// If it is not host node and, we might have a host node inside it.// Try to search down until we find one.if (node.flags & Placement) {// If we don't have a child, try the siblings instead.continue siblings;} // If we don't have a child, try the siblings instead.// We also skip portals because they are not part of this host tree.if (node.child === null || node.tag === HostPortal) {continue siblings;} else {node.child.return = node;node = node.child;}} // Check if this host node is stable or about to be placed.if (!(node.flags & Placement)) {// Found it!return node.stateNode;}}}
有两层while循环,原因
上述Demo,从fiber树角度,一次是FiberRootNode、当前dom的Fiber节点(React.render产生的RootFiber)、RootFiber的子节点依次是App Fiber,div Fiber,Item Fiber,li Fiber。
从Dom树角度,当前的根节点是#root div,子节点依次是div、li。
可见,dom的结构与Fiber的结构并不对应。
如果在item前插入p节点,如何插入?dom数中是在li调用insertBefore插入p。所以在Fiber节点中,需要先找到li。但是p对应的兄弟节点不是li,而是Item。Item的子Fiber节点才是li,因此我们要寻找fiber节点的兄弟节点,这个查找可能是跨层级的,因此getHostSibling有两层循环。
当执行玩commitComponent以后,dom节点已经插入dom数中,需要删除Placement。
PlacementAndUpdate

要先调用插入的commitPlacement,再调update的commitWork。
commitWork
FunctionComponent
FunctionComponent、ForwardRef、MemoComponent、SimpleComponent、Block,这些节点都与FunctionComponent相关,这些case调用commitHookEffecListUnmount,会调用useLayoutEffect的销毁函数。
commitHookEffecListUnmount:会遍历effectList,如果出入的effect包含了传入的tag(hookLayout),需要先执行他们的销毁函数,即useLayoutEffect回调函数的返回值。所以在执行任何effctTag函数之前,都会执行useLayoutEffect的销毁函数。
HostComponnet
HostComponnet:会调用commitUpdate方法,接受的参数updatePayload,即updateQueue属性(Fiber节点props和children等属性改变的值,第i是key,第i+1项是value)。
commitUpdate调用UpdateProperties来更新dom的属性
Deletion
commitDeletion
支持mutation,调用unmoutHostComponents
unmoutHostComponents
要先删除一个节点,需先找到这个节点的父级节点,
根据不同的tag,找到父级Dom节点。
findParent: while (parent !== null) {switch (parent.tag) {case HostComponent:{hostParent = parent.stateNode;hostParentIsContainer = false;break findParent;}case HostRoot:{hostParent = parent.stateNode.containerInfo;hostParentIsContainer = true;break findParent;}case HostPortal:{hostParent = parent.stateNode.containerInfo;hostParentIsContainer = true;break findParent;}}
HostComponnet
找到父级节点后,执行commitNestUnmounts,嵌套的执行unMount,因为当我们要删除一个Fiber节点时,这个Fiber节点可能是一颗子树,这颗Fiber节点的所有的子、孙节点都需要递归删除。
删除操作:commitUnmout中

对于FunctionComponent节点,需要执行enqueuePendingPassiveHookEffectUnmout(注册需要执行的useEffect的回调函数)。所以当FunctionComponent包含useEffect,那么销毁时也会执行useEffect的函数。
对于ClassComponent时,会执行componentWillUnmout生命周期钩子。
对于HostComponent,会解绑他的Ref属性。
总结
mutation阶段,会遍历包含effectTag Fiber的链表,判断这些Fiber节点包含的effectTag,分别做对应的操作,包括重置文本节点、解绑Ref或更新Ref、插入Dom、跟新Dom、删除Dom和ssr等相关操作。
