引言
书接上回,上一篇文章主要是介绍了 React 初次渲染过程中在内存中生成 workInProgress树的过程。主要是经历了两个阶段,一个是 beginWork,另一个是 completeWork。那React 是如何将已经生成的 WorkInProgress 树渲染到宿主环境中的呢?接下来说一说 commit 阶段,也就是将 workInProgress 树渲染到页面上的过程。
原理
demo
因为 commit 阶段涉及到副作用的执行,比如 useLayoutEffect 等。 所以我们改造一下调试源码用的组件代码。
import React, { useEffect, useState } from "react"
const Index = ()=>{
debugger;
const [state,setState] = useState(0)
useEffect(()=>{
console.log(123)
},[])
console.log('渲染吗??')
const [demo,setDemo] = useState('demo')
return <div>
{state}
<button onClick={()=>{
// debugger
// setState(1)
// setState(10)
// setState(100)
setState(0)
}}>触发渲染</button>
</div>
}
export default Index
我们的组件大概是这个样子的。下面我们来看一下 React 整个过程是怎么样的。
过程
首先看一下当走完beginWork 和completeWork 之后,生成的树是怎么样的。
可以看到,最后是生成了一个这样的树,我们的 wip 树保存在finishedWork 中,可以打印出来看一下
进入 commit 阶段的入口函数是 commitRoot
function commitRoot(root) {
// TODO: This no longer makes any sense. We already wrap the mutation and
// layout phases. Should be able to remove.
var previousUpdateLanePriority = getCurrentUpdatePriority();
var prevTransition = ReactCurrentBatchConfig$3.transition;
try {
ReactCurrentBatchConfig$3.transition = 0;
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(root, previousUpdateLanePriority);
} finally {
ReactCurrentBatchConfig$3.transition = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority);
}
return null;
}
下面从这个函数出发具体来看看 React 做了什么
commitRoot
commitRoot 主要的逻辑就是调用了一个commitRootImpl,同时传入了 root 树和previousUpdateLanePriority。
commitRootImpl
commitRootImpl 的作用就大了许多,我们分别来看commitRootImpl 做了哪些事情。
function commitRootImpl(root, renderPriorityLevel) {
do {
// 清空副作用列表
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
flushRenderPhaseStrictModeWarningsInDEV();
// 判断当前 fiber 节点的子孙节点是否有副作用
var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
// 判断当前节点是否有副作用
var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
var shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(root, finishedWork);
commitMutationEffects(root, finishedWork, lanes);
commitLayoutEffects(finishedWork, root, lanes);
{
markLayoutEffectsStopped();
}
// opportunity to paint.
requestPaint();
return null;
}
从源码可以看到,commitRootImpl 主要做了三件事情
- 通过一个while 循环去调用flushPassiveEffects去清除副作用,防止因为有没有运行的副作用影响 commit过程。
- 判断子孙节点以及自己是否有副作用
- 如果有副作用,依次执行commitBeforeMutationEffects,commitMutationEffects,commitLayoutEffects,requestPaint
- 如果没有副作用,直接切树
- 其他一些收尾工作。
下面我们依次进行说明
判断副作用
在completeWork 阶段,React 将子组件的 flags 都冒泡到了父组件的subtreeFlags 中,所以可以通过subtreeFlags来检查子组件是否有副作用已经自己本身是否有副作用。
var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
如果有副作用的话,就依次运行commitBeforeMutationEffects ,commitMutationEffects,commitLayoutEffects,requestPaint。下面分别进行说明。 如果友友们对位运算不太熟悉,可以自行百度了解一下~
commitBeforeMutationEffects
function commitBeforeMutationEffects(root, firstChild) {
focusedInstanceHandle = prepareForCommit(root.containerInfo);
nextEffect = firstChild;
commitBeforeMutationEffects_begin(); // We no longer need to track the active instance fiber
var shouldFire = shouldFireAfterActiveInstanceBlur;
shouldFireAfterActiveInstanceBlur = false;
focusedInstanceHandle = null;
return shouldFire;
}
主要流程是commitBeforeMutationEffects_begin处理的,我们直接进入commitBeforeMutationEffects_begin查看 React 做了什么。
commitBeforeMutationEffects_begin
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
var fiber = nextEffect; // This phase is only used for beforeActiveInstanceBlur.
var child = fiber.child;
if ((fiber.subtreeFlags & BeforeMutationMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}
}
}
主要逻辑在commitBeforeMutationEffects_complete中
commitBeforeMutationEffects_complete
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
var fiber = nextEffect;
setCurrentFiber(fiber);
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
reportUncaughtErrorInDEV(error);
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentFiber();
var sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
这里解释一下,这个循环的作用主要是运行了commitBeforeMutationEffectsOnFiber,同时如果当前 fiber 节点有sibling节点的话,也会运行commitBeforeMutationEffects_complete。整个逻辑是一个从下到上的过程。
commitBeforeMutationEffectsOnFiber
function commitBeforeMutationEffectsOnFiber(finishedWork) {
var current = finishedWork.alternate;
var flags = finishedWork.flags;
if ((flags & Snapshot) !== NoFlags) {
setCurrentFiber(finishedWork);
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
{
break;
}
case ClassComponent:
{
if (current !== null) {
var prevProps = current.memoizedProps;
var prevState = current.memoizedState;
var instance = finishedWork.stateNode; // We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
if (instance.props !== finishedWork.memoizedProps) {
error('Expected %s props to match memoized props before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
if (instance.state !== finishedWork.memoizedState) {
error('Expected %s state to match memoized state before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
}
}
var snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState);
{
var didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate;
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
error('%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + 'must be returned. You have returned undefined.', getComponentNameFromFiber(finishedWork));
}
}
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot:
{
{
var root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
break;
default:
{
throw new Error('This unit of work tag should not have side-effects. This error is ' + 'likely caused by a bug in React. Please file an issue.');
}
}
resetCurrentFiber();
}
}
这里判断当前 fiber 是否有Snapshot副作用,如果有副作用的话,根据 tag 进入不同的 case,主要有作用的 case 就两个,其余都是凑数的,没什么实际用途。
case ClassComponent:
{
if (current !== null) {
var prevProps = current.memoizedProps;
var prevState = current.memoizedState;
var instance = finishedWork.stateNode; // We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
if (instance.props !== finishedWork.memoizedProps) {
error('Expected %s props to match memoized props before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
if (instance.state !== finishedWork.memoizedState) {
error('Expected %s state to match memoized state before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
}
}
var snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState);
{
var didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate;
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
error('%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + 'must be returned. You have returned undefined.', getComponentNameFromFiber(finishedWork));
}
}
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot:
{
{
var root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
如果是一个类组件的话,会执行getSnapshotBeforeUpdate这个函数来做一些事情。因为对类组件了解不够深,这里跳过说明。 如果是HostRoot,会清除节点内容。 具体可看下面代码
function clearContainer(container) {
if (container.nodeType === ELEMENT_NODE) {
container.textContent = '';
} else if (container.nodeType === DOCUMENT_NODE) {
var body = container.body;
if (body != null) {
body.textContent = '';
}
}
}
这里是为了防止本身 html 节点中有内容,影响我们 React 的整个渲染。 下面我们用图来看一下整个执行过程
commitMutationEffects
和上面的流程差不多,看代码
function commitMutationEffects(root, firstChild, committedLanes) {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = firstChild;
commitMutationEffects_begin(root);
inProgressLanes = null;
inProgressRoot = null;
}
依旧是commitMutationEffects_begin来进行一些逻辑的处理。
commitMutationEffects_begin
function commitMutationEffects_begin(root) {
while (nextEffect !== null) {
var fiber = nextEffect; // TODO: Should wrap this in flags check, too, as optimization
var deletions = fiber.deletions;
if (deletions !== null) {
for (var i = 0; i < deletions.length; i++) {
var childToDelete = deletions[i];
try {
commitDeletion(root, childToDelete, fiber);
} catch (error) {
reportUncaughtErrorInDEV(error);
captureCommitPhaseError(childToDelete, fiber, error);
}
}
}
var child = fiber.child;
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitMutationEffects_complete(root);
}
}
}
该函数主要做了两件事:
- 如果存在deletions ,先处理这部分的逻辑。
- 然后进入commitMutationEffects_complete。
commitMutationEffects_complete
function commitMutationEffects_complete(root) {
while (nextEffect !== null) {
var fiber = nextEffect;
setCurrentFiber(fiber);
try {
commitMutationEffectsOnFiber(fiber, root);
} catch (error) {
reportUncaughtErrorInDEV(error);
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentFiber();
var sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
依旧是熟悉的套路,先执行commitMutationEffectsOnFiber,然后再去判断当前节点是否有sibling,如果有sibling,继续执行commitMutationEffectsOnFiber。
commitMutationEffectsOnFiber
function commitMutationEffectsOnFiber(finishedWork, root) {
var primaryFlags = flags & (Placement | Update | Hydrating);
switch (primaryFlags) {
case Placement:
{
commitPlacement(finishedWork); // Clear the "placement" from effect tag so that
finishedWork.flags &= ~Placement;
break;
}
case PlacementAndUpdate:
{
commitPlacement(finishedWork); // Clear the "placement" from effect tag so that
finishedWork.flags &= ~Placement; // Update
var _current3 = finishedWork.alternate;
commitWork(_current3, finishedWork);
break;
}
case Hydrating:
{
finishedWork.flags &= ~Hydrating;
break;
}
case HydratingAndUpdate:
{
finishedWork.flags &= ~Hydrating; // Update
var _current4 = finishedWork.alternate;
commitWork(_current4, finishedWork);
break;
}
case Update:
{
var _current5 = finishedWork.alternate;
/*KaSong*/logHook('updateDOM', finishedWork, 'commitWork')
commitWork(_current5, finishedWork);
break;
}
}
}
主要是判断当前 flags,通过 flags 进入不同的 case 进行处理。初次渲染的话,进入的是Placement分支,主要是执行了以下方法:commitPlacement
function commitPlacement(finishedWork) {
var parentFiber = getHostParentFiber(finishedWork); // Note: these two variables *must* always be updated together.
var parent;
var isContainer;
var parentStateNode = parentFiber.stateNode;
switch (parentFiber.tag) {
case HostComponent:
parent = parentStateNode;
isContainer = false;
break;
case HostRoot:
parent = parentStateNode.containerInfo;
isContainer = true;
break;
case HostPortal:
parent = parentStateNode.containerInfo;
isContainer = true;
break;
// eslint-disable-next-line-no-fallthrough
default:
throw new Error('Invalid host parent fiber. This error is likely caused by a bug ' + 'in React. Please file an issue.');
}
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.
if (isContainer) {
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else {
insertOrAppendPlacementNode(finishedWork, before, parent);
}
}
通过找到当前节点的父节点parentFiber,执行插入方法insertOrAppendPlacementNodeIntoContainer。
insertOrAppendPlacementNodeIntoContainer
function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
var tag = node.tag;
var isHost = tag === HostComponent || tag === HostText;
if (isHost) {
var stateNode = node.stateNode;
if (before) {
insertInContainerBefore(parent, stateNode, before);
} else {
appendChildToContainer(parent, stateNode);
}
} else if (tag === HostPortal) ; else {
var child = node.child;
if (child !== null) {
insertOrAppendPlacementNodeIntoContainer(child, before, parent);
var sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
这个方法就是一个递归的过程,将真实的元素插入到指定的位置。这里有一个判断,只有节点的 tag 为 HostComponent 或者 tag 为 HostText,才代表是可以插入的,执行插入逻辑。
commitLayoutEffects
function commitLayoutEffects(finishedWork, root, committedLanes) {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = finishedWork;
commitLayoutEffects_begin(finishedWork, root, committedLanes);
inProgressLanes = null;
inProgressRoot = null;
}
还是一样的套路,具体的逻辑通过commitLayoutEffects_begin进行执行。
commitLayoutEffects_begin
function commitLayoutEffects_begin(subtreeRoot, root, committedLanes) {
// Suspense layout effects semantics don't change for legacy roots.
var isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode;
while (nextEffect !== null) {
var fiber = nextEffect;
var firstChild = fiber.child;
if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild;
} else {
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
}
}
}
还是一样的套路,开启一个循环,从上到下遍历,同时执行commitLayoutMountEffects_complete
commitLayoutMountEffects_complete
function commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes) {
while (nextEffect !== null) {
var fiber = nextEffect;
if ((fiber.flags & LayoutMask) !== NoFlags) {
var current = fiber.alternate;
setCurrentFiber(fiber);
try {
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
} catch (error) {
reportUncaughtErrorInDEV(error);
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentFiber();
}
if (fiber === subtreeRoot) {
nextEffect = null;
return;
}
var sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
一样的套路,开启了一个循环,去执行commitLayoutEffectOnFiber以及判断当前节点是否有sibling,如果有sibling,继续执行commitLayoutEffectOnFiber。
commitLayoutEffectOnFiber
function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork, committedLanes) {
if ((finishedWork.flags & LayoutMask) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
{
if ( !offscreenSubtreeWasHidden) {
// At this point layout effects have already been destroyed (during mutation phase).
// This is done to prevent 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 ( finishedWork.mode & ProfileMode) {
try {
startLayoutEffectTimer();
commitHookEffectListMount(Layout | HasEffect, finishedWork);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
commitHookEffectListMount(Layout | HasEffect, finishedWork);
}
}
break;
}
case ClassComponent:
{
var instance = finishedWork.stateNode;
if (finishedWork.flags & Update) {
if (!offscreenSubtreeWasHidden) {
if (current === null) {
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
if (instance.props !== finishedWork.memoizedProps) {
error('Expected %s props to match memoized props before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
if (instance.state !== finishedWork.memoizedState) {
error('Expected %s state to match memoized state before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
}
}
if ( finishedWork.mode & ProfileMode) {
try {
startLayoutEffectTimer();
instance.componentDidMount();
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidMount();
}
} else {
var prevProps = finishedWork.elementType === finishedWork.type ? current.memoizedProps : resolveDefaultProps(finishedWork.type, current.memoizedProps);
var prevState = current.memoizedState; // We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
if (instance.props !== finishedWork.memoizedProps) {
error('Expected %s props to match memoized props before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
if (instance.state !== finishedWork.memoizedState) {
error('Expected %s state to match memoized state before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
}
}
if ( finishedWork.mode & ProfileMode) {
try {
startLayoutEffectTimer();
instance.componentDidUpdate(prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidUpdate(prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate);
}
}
}
} // TODO: I think this is now always non-null by the time it reaches the
// commit phase. Consider removing the type check.
var updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
if (instance.props !== finishedWork.memoizedProps) {
error('Expected %s props to match memoized props before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
if (instance.state !== finishedWork.memoizedState) {
error('Expected %s state to match memoized state before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
}
} // We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostRoot:
{
// TODO: I think this is now always non-null by the time it reaches the
// commit phase. Consider removing the type check.
var _updateQueue = finishedWork.updateQueue;
if (_updateQueue !== null) {
var _instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostComponent:
_instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
_instance = finishedWork.child.stateNode;
break;
}
}
commitUpdateQueue(finishedWork, _updateQueue, _instance);
}
break;
}
case HostComponent:
{
var _instance2 = finishedWork.stateNode; // Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (current === null && finishedWork.flags & Update) {
var type = finishedWork.type;
var props = finishedWork.memoizedProps;
commitMount(_instance2, type, props);
}
break;
}
case HostText:
{
// We have no life-cycles associated with text.
break;
}
case HostPortal:
{
// We have no life-cycles associated with portals.
break;
}
case Profiler:
{
{
var _finishedWork$memoize2 = finishedWork.memoizedProps,
onCommit = _finishedWork$memoize2.onCommit,
onRender = _finishedWork$memoize2.onRender;
var effectDuration = finishedWork.stateNode.effectDuration;
var commitTime = getCommitTime();
var phase = current === null ? 'mount' : 'update';
{
if (isCurrentUpdateNested()) {
phase = 'nested-update';
}
}
if (typeof onRender === 'function') {
onRender(finishedWork.memoizedProps.id, phase, finishedWork.actualDuration, finishedWork.treeBaseDuration, finishedWork.actualStartTime, commitTime);
}
{
if (typeof onCommit === 'function') {
onCommit(finishedWork.memoizedProps.id, phase, effectDuration, commitTime);
} // Schedule a passive effect for this Profiler to call onPostCommit hooks.
// This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
// because the effect is also where times bubble to parent Profilers.
enqueuePendingPassiveProfilerEffect(finishedWork); // Propagate layout effect durations to the next nearest Profiler ancestor.
// Do not reset these values until the next render so DevTools has a chance to read them first.
var parentFiber = finishedWork.return;
outer: while (parentFiber !== null) {
switch (parentFiber.tag) {
case HostRoot:
var root = parentFiber.stateNode;
root.effectDuration += effectDuration;
break outer;
case Profiler:
var parentStateNode = parentFiber.stateNode;
parentStateNode.effectDuration += effectDuration;
break outer;
}
parentFiber = parentFiber.return;
}
}
}
break;
}
case SuspenseComponent:
{
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
break;
}
case SuspenseListComponent:
case IncompleteClassComponent:
case ScopeComponent:
case OffscreenComponent:
case LegacyHiddenComponent:
break;
default:
throw new Error('This unit of work tag should not have side-effects. This error is ' + 'likely caused by a bug in React. Please file an issue.');
}
}
if ( !offscreenSubtreeWasHidden) {
{
if (finishedWork.flags & Ref) {
commitAttachRef(finishedWork);
}
}
}
}
- 判断当前flags 是否存在LayoutMask副作用
- 如果存在LayoutMask副作用,根据当前的 fiber 节点的 tag 进入不同的分支处理。 我们主要分析函数式组件
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
{
if ( !offscreenSubtreeWasHidden) {
// At this point layout effects have already been destroyed (during mutation phase).
// This is done to prevent 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 ( finishedWork.mode & ProfileMode) {
try {
startLayoutEffectTimer();
commitHookEffectListMount(Layout | HasEffect, finishedWork);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
commitHookEffectListMount(Layout | HasEffect, finishedWork);
}
}
break;
}
如果是函数式组件的话,进入commitHookEffectListMount。
commitHookEffectListMount
function commitHookEffectListMount(flags, finishedWork) {
var updateQueue = finishedWork.updateQueue;
var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
var firstEffect = lastEffect.next;
var effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
{
if ((flags & Passive$1) !== NoFlags$1) {
markComponentPassiveEffectMountStarted(finishedWork);
} else if ((flags & Layout) !== NoFlags$1) {
markComponentLayoutEffectMountStarted(finishedWork);
}
} // Mount
var create = effect.create;
effect.destroy = create();
{
if ((flags & Passive$1) !== NoFlags$1) {
markComponentPassiveEffectMountStopped();
} else if ((flags & Layout) !== NoFlags$1) {
markComponentLayoutEffectMountStopped();
}
}
{
var destroy = effect.destroy;
if (destroy !== undefined && typeof destroy !== 'function') {
var hookName = void 0;
if ((effect.tag & Layout) !== NoFlags) {
hookName = 'useLayoutEffect';
} else if ((effect.tag & Insertion) !== NoFlags) {
hookName = 'useInsertionEffect';
} else {
hookName = 'useEffect';
}
var addendum = void 0;
if (destroy === null) {
addendum = ' You returned null. If your effect does not require clean ' + 'up, return undefined (or nothing).';
} else if (typeof destroy.then === 'function') {
addendum = '\n\nIt looks like you wrote ' + hookName + '(async () => ...) or returned a Promise. ' + 'Instead, write the async function inside your effect ' + 'and call it immediately:\n\n' + hookName + '(() => {\n' + ' async function fetchData() {\n' + ' // You can await here\n' + ' const response = await MyAPI.getData(someId);\n' + ' // ...\n' + ' }\n' + ' fetchData();\n' + "}, [someId]); // Or [] if effect doesn't need props or state\n\n" + 'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching';
} else {
addendum = ' You returned: ' + destroy;
}
error('%s must not return anything besides a function, ' + 'which is used for clean-up.%s', hookName, addendum);
}
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
updateQueue 是存储副作用的地方,可以看一下updateQueue 的数据结构是怎么样的:
其中 create 就是我们定义的 useLayoutEffecct 中的函数。当我们有多个副作用函数时,会通过 next 进行连接,这里不展开讲了,后续讲副作用的时候会补这方面的知识。
do {
if ((effect.tag & flags) === flags) {
{
if ((flags & Passive$1) !== NoFlags$1) {
markComponentPassiveEffectMountStarted(finishedWork);
} else if ((flags & Layout) !== NoFlags$1) {
markComponentLayoutEffectMountStarted(finishedWork);
}
} // Mount
var create = effect.create;
effect.destroy = create();
{
if ((flags & Passive$1) !== NoFlags$1) {
markComponentPassiveEffectMountStopped();
} else if ((flags & Layout) !== NoFlags$1) {
markComponentLayoutEffectMountStopped();
}
}
{
var destroy = effect.destroy;
if (destroy !== undefined && typeof destroy !== 'function') {
var hookName = void 0;
if ((effect.tag & Layout) !== NoFlags) {
hookName = 'useLayoutEffect';
} else if ((effect.tag & Insertion) !== NoFlags) {
hookName = 'useInsertionEffect';
} else {
hookName = 'useEffect';
}
var addendum = void 0;
if (destroy === null) {
addendum = ' You returned null. If your effect does not require clean ' + 'up, return undefined (or nothing).';
} else if (typeof destroy.then === 'function') {
addendum = '\n\nIt looks like you wrote ' + hookName + '(async () => ...) or returned a Promise. ' + 'Instead, write the async function inside your effect ' + 'and call it immediately:\n\n' + hookName + '(() => {\n' + ' async function fetchData() {\n' + ' // You can await here\n' + ' const response = await MyAPI.getData(someId);\n' + ' // ...\n' + ' }\n' + ' fetchData();\n' + "}, [someId]); // Or [] if effect doesn't need props or state\n\n" + 'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching';
} else {
addendum = ' You returned: ' + destroy;
}
error('%s must not return anything besides a function, ' + 'which is used for clean-up.%s', hookName, addendum);
}
}
}
effect = effect.next;
} while (effect !== firstEffect);
这个函数是具体执行副作用的地方,首先判断updateQueue是否为 null。 如果不等于null 的情况下,会依次执行副作用。同时循环的结束条件是整个链表全部执行完毕,因为这个链表是一个环状链表,所以判断结束的条件是 effect === firstEffect。
总结
commit 阶段是将 React 构造出来的树渲染到屏幕上的阶段,首先会进行两次副作用判断,一次是判断子孙是否有副作用,另一次是判断自己是否有副作用。 如果有副作用就会进入三个阶段,分别是commitBeforeMutationEffects,commitMutationEffects,commitLayoutEffects,requestPaint。
commitBeforeMutationEffects 主要的作用是通过判断当前的 flags是否存在Snapshot 副作用。如果存在副作用,根据 fiber 的 tag 是 class 还是函数式组件,如果是 class,会调用getSnapshotBeforeUpdate。如果是函数式组件,会 clear 容器的一些内容。
commitMutationEffects主要作用是判断当前是否有要删除的副作用fiber,如果 fiber 有删除的节点。 先进行删除操作,同时会将真实 dom 节点插入到指定的位置。
commitLayoutEffects主要是判断当前是否有 useLayoutEffect 副作用,如果有副作用,执行副作用函数,并在合适的时机执行cleanUp清理函数。