function Child() {
return <div>123</div>
}
export default function Index() {
const [isShow, setIsShow] = useState(true)
return <>
<button onClick={() => setIsShow(v => !v)}>{isShow.toString()}</button>
{isShow && <div>hhahh
<Child />
</div>}
{!isShow && <div>add</div>}
</>
}
fiber 通过performUnitOfWork进行了diff以及标记delete、insert,下面就要在commitRoot阶段将fiber同步到dom
function commitMutationEffects(root, finishedWork, committedLanes) {
inProgressLanes = committedLanes;
inProgressRoot = root;
setCurrentFiber(finishedWork);
commitMutationEffectsOnFiber(finishedWork, root);
setCurrentFiber(finishedWork);
inProgressLanes = null;
inProgressRoot = null;
}
从 root fiberNode进入 递归到Index fiber进入commitMutationEffectsOnFiber
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
var current = finishedWork.alternate;
var flags = finishedWork.flags;
switch (finishedWork.tag) {
// ......
case SimpleMemoComponent:
{
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
// ....... useEffect
}
}
}
大多数case都会先进入recursivelyTraverseMutationEffects
function recursivelyTraverseMutationEffects(root, parentFiber, lanes) {
// Deletions effects can be scheduled on any fiber type. They need to happen
// before the children effects hae fired.
var deletions = parentFiber.deletions;
if (deletions !== null) {
for (var i = 0; i < deletions.length; i++) {
var childToDelete = deletions[i];
try {
commitDeletionEffects(root, parentFiber, childToDelete);
} catch (error) {
captureCommitPhaseError(childToDelete, parentFiber, error);
}
}
}
var prevDebugFiber = getCurrentFiber();
if (parentFiber.subtreeFlags & MutationMask) {
var child = parentFiber.child;
while (child !== null) {
setCurrentFiber(child);
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
setCurrentFiber(prevDebugFiber);
}
删除
首先检查fiber.deletions,因为后续操作会对fiber进行遍历,需要先把fiber的结构调整正确
如果没有删除的标记,就会继续向下遍历子节点,子节点遍历完之后再遍历兄弟节点,递归调用commitMutationEffectsOnFiber直到进入 Index fiber,然后进入到删除<div>hhahh <Child /> </div>这个fiber
var hostParent = null;
var hostParentIsContainer = false;
function commitDeletionEffects(root, returnFiber, deletedFiber) {
{
// We only have the top Fiber that was deleted but we need to recurse down its
// children to find all the terminal nodes.
// Recursively delete all host nodes from the parent, detach refs, clean
// up mounted layout effects, and call componentWillUnmount.
// We only need to remove the topmost host child in each branch. But then we
// still need to keep traversing to unmount effects, refs, and cWU. TODO: We
// could split this into two separate traversals functions, where the second
// one doesn't include any removeChild logic. This is maybe the same
// function as "disappearLayoutEffects" (or whatever that turns into after
// the layout phase is refactored to use recursion).
// Before starting, find the nearest host parent on the stack so we know
// which instance/container to remove the children from.
// TODO: Instead of searching up the fiber return path on every deletion, we
// can track the nearest host component on the JS stack as we traverse the
// tree during the commit phase. This would make insertions faster, too.
var parent = returnFiber;
findParent: while (parent !== null) {
switch (parent.tag) {
case HostSingleton:
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;
}
}
parent = parent.return;
}
if (hostParent === null) {
throw new Error('Expected to find a host parent. This error is likely caused by ' + 'a bug in React. Please file an issue.');
}
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
hostParent = null;
hostParentIsContainer = false;
}
detachFiberMutation(deletedFiber);
}
首先找到删除节点的真实父dom节点,赋值给hostParent,这里是body,之后进入commitDeletionEffectsOnFiber
function commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, deletedFiber) {
onCommitUnmount(deletedFiber); // The cases in this outer switch modify the stack before they traverse
// into their subtree. There are simpler cases in the inner switch
// that don't modify the stack.
switch (deletedFiber.tag) {
// ......
case HostComponent:
{
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
} // Intentional fallthrough to next branch
}
case HostText: {
// ......
}
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent:
{
// ......
}
}
}
首先会进入HostComponent这个分支
case HostComponent:
{
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
} // Intentional fallthrough to next branch
}
将ref引用给清空
然后进入HostText
case HostText:
{
// We only need to remove the nearest host child. Set the host parent
// to `null` on the stack to indicate that nested children don't
// need to be removed.
{
var _prevHostParent = hostParent;
var _prevHostParentIsContainer = hostParentIsContainer;
hostParent = null;
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
hostParent = _prevHostParent;
hostParentIsContainer = _prevHostParentIsContainer;
if (hostParent !== null) {
// Now that all the child effects have unmounted, we can remove the
// node from the tree.
if (hostParentIsContainer) {
removeChildFromContainer(hostParent, deletedFiber.stateNode);
} else {
removeChild(hostParent, deletedFiber.stateNode);
}
}
}
return;
}
注意这里将hostParent清空了
递归删除
然后进入recursivelyTraverseDeletionEffects
function recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, parent) {
// TODO: Use a static flag to skip trees that don't have unmount effects
var child = parent.child;
while (child !== null) {
commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
child = child.sibling;
}
}
此时的child 是hhahh,进行child的删除
commitDeletionEffectsOnFiber进入到HostText分支
由于hhahh没有 child fiber,不会继续递归
此时hostParent是null,不会删除,删除操作在一开始标记的dom那里一口气删除
之后执行删除hhahh 的sibling节点 Child fiber
case FunctionComponent:
{
if (!offscreenSubtreeWasHidden) {
var updateQueue = deletedFiber.updateQueue;
if (updateQueue !== null) {
var lastEffect = updateQueue.lastEffect;
if (lastEffect !== null) {
var firstEffect = lastEffect.next;
var effect = firstEffect;
do {
var tag = effect.tag;
var inst = effect.inst;
var destroy = inst.destroy;
if (destroy !== undefined) {
if ((tag & Insertion) !== NoFlags) {
inst.destroy = undefined;
safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy);
} else if ((tag & Layout) !== NoFlags) {
{
markComponentLayoutEffectUnmountStarted(deletedFiber);
}
if (shouldProfile(deletedFiber)) {
startLayoutEffectTimer();
inst.destroy = undefined;
safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy);
recordLayoutEffectDuration(deletedFiber);
} else {
inst.destroy = undefined;
safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy);
}
{
markComponentLayoutEffectUnmountStopped();
}
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
}
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
return;
}
删除 FunctionComponent节点需要将触发它存放的effect的销毁回调(如果存在的话), 然后又继续删除 Child的child fiber
回到触发删除的fiber
等它们全部处理完,回到<div>hhahh <Child /> </div>
removeChild(hostParent, deletedFiber.stateNode);
将这个dom从body删除
function detachFiberMutation(fiber) {
// Cut off the return pointer to disconnect it from the tree.
// This enables us to detect and warn against state updates on an unmounted component.
// It also prevents events from bubbling from within disconnected components.
//
// Ideally, we should also clear the child pointer of the parent alternate to let this
// get GC:ed but we don't know which for sure which parent is the current
// one so we'll settle for GC:ing the subtree of this child.
// This child itself will be GC:ed when the parent updates the next time.
//
// Note that we can't clear child or sibling pointers yet.
// They're needed for passive effects and for findDOMNode.
// We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).
//
// Don't reset the alternate yet, either. We need that so we can detach the
// alternate's fields in the passive phase. Clearing the return pointer is
// sufficient for findDOMNode semantics.
var alternate = fiber.alternate;
if (alternate !== null) {
alternate.return = null;
}
fiber.return = null;
}
然后将<div>hhahh <Child /> </div>这个fiber剔除出去
更新
回到 Index的 recursivelyTraverseMutationEffects
删除处理完成之后,执行子节点 button 的commitMutationEffectsOnFiber
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
var current = finishedWork.alternate;
var flags = finishedWork.flags;
switch (finishedWork.tag) {
case HostComponent:
{
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(current, current.return);
}
}
{
// TODO: ContentReset gets cleared by the children during the commit
// phase. This is a refactor hazard because it means we must read
// flags the flags after `commitReconciliationEffects` has already run;
// the order matters. We should refactor so that ContentReset does not
// rely on mutating the flag during commit. Like by setting a flag
// during the render phase instead.
if (finishedWork.flags & ContentReset) {
var instance = finishedWork.stateNode;
try {
resetTextContent(instance);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
if (flags & Update) {
var _instance2 = finishedWork.stateNode;
if (_instance2 != null) {
// Commit the work prepared earlier.
var newProps = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
var oldProps = current !== null ? current.memoizedProps : newProps;
var type = finishedWork.type; // TODO: Type the updateQueue to be specific to host components.
var _updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
try {
commitUpdate(_instance2, _updatePayload, type, oldProps, newProps, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
}
return;
}
}
}
由于没有删除内容,进入commitReconciliationEffects
没有插入,继续往下执行,处理更新
commitUpdate(_instance2, _updatePayload, type, oldProps, newProps, finishedWork);
function commitUpdate(domElement, updatePayload, type, oldProps, newProps, internalInstanceHandle) {
// Diff and update the properties.
updateProperties(domElement, type, oldProps, newProps); // Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
}
将button dom.props上的内容更新,然后将button里面的TextNodeValue true -》 false
再将fiber上的props也进行更新
插入
回到 Index的 recursivelyTraverseMutationEffects
button更新完成之后,执行子节点 <div>add</div> 的commitMutationEffectsOnFiber
进入commitReconciliationEffects
function commitReconciliationEffects(finishedWork) {
// Placement effects (insertions, reorders) can be scheduled on any fiber
// type. They needs to happen after the children effects have fired, but
// before the effects on this fiber have fired.
var flags = finishedWork.flags;
if (flags & Placement) {
try {
commitPlacement(finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
} // Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted does
// and isMounted is deprecated anyway so we should be able to kill this.
finishedWork.flags &= ~Placement;
}
if (flags & Hydrating) {
finishedWork.flags &= ~Hydrating;
}
}
function commitPlacement(finishedWork) {
{
if (finishedWork.tag === HostSingleton) {
// Singletons are already in the Host and don't need to be placed
// Since they operate somewhat like Portals though their children will
// have Placement and will get placed inside them
return;
}
} // Recursively insert all host nodes into the parent.
var parentFiber = getHostParentFiber(finishedWork);
switch (parentFiber.tag) {
case HostSingleton:
{
{
var parent = parentFiber.stateNode;
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;
} // Fall through
}
case HostComponent:
{
var _parent = parentFiber.stateNode;
if (parentFiber.flags & ContentReset) {
// Reset the text content of the parent before doing any insertions
resetTextContent(_parent); // Clear ContentReset from the effect tag
parentFiber.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 _parent2 = parentFiber.stateNode.containerInfo;
var _before2 = getHostSibling(finishedWork);
insertOrAppendPlacementNodeIntoContainer(finishedWork, _before2, _parent2);
break;
}
default:
throw new Error('Invalid host parent fiber. This error is likely caused by a bug ' + 'in React. Please file an issue.');
}
}
首先找到 <div>add</div>的parent Fiber 就是 body
function insertOrAppendPlacementNode(node, before, parent) {
var tag = node.tag;
var isHost = tag === HostComponent || tag === HostText;
if (isHost) {
var stateNode = node.stateNode;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else if (tag === HostPortal || (tag === HostSingleton )) ; 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;
}
}
}
}
需要差到基准前的话用,insertBefore,不然直接查到末尾用appendChild就好
最后回到Index 的 commitDeletionEffectsOnFiber 中
case SimpleMemoComponent:
{
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
try {
commitHookEffectListUnmount(Insertion | HasEffect, finishedWork, finishedWork.return);
commitHookEffectListMount(Insertion | HasEffect, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
} // Layout effects are destroyed during the mutation phase so that all
// destroy functions for all fibers are called before any create functions.
// This prevents sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.
if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
commitHookEffectListUnmount(Layout | HasEffect, finishedWork, finishedWork.return);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
recordLayoutEffectDuration(finishedWork);
} else {
try {
commitHookEffectListUnmount(Layout | HasEffect, finishedWork, finishedWork.return);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
return;
}
根据优先级,先后调用Insertion.destory - Insertion - layout.destory
后面layout的effect会在commitMutationEffects之后的commitLayoutEffects阶段进行
总结
commitMutationEffects的顺序依次是:删除-插入-更新,需要保证fiber结构然后再执行更新