SoFunction
Updated on 2025-03-02

The difference between useEffect and useLayoutEffect in React

Pre-knowledge

We can divide React's workflow into several large pieces:

  1. render stage: mainly generates Fiber nodes and builds a complete Fiber tree
  2. commit stage: In the previous render stage, a side effect link will be generated on rootFiber, and the applied DOM operation will be executed in this stage

The work in the commit stage is mainly divided into three parts, and the corresponding function name in the source code is:

  • commitBeforeMutationEffects stage: mainly deals with some related operations before performing DOM operations
  • commitMutationEffects stage: perform DOM operations
  • commitLayoutEffects stage: mainly deals with some related operations after performing DOM operations

The difference between useEffect and useLayoutEffect is mainly reflected in the processing of these three stages. The conclusion is: useEffect will execute its response function and the last destroy function asynchronously, while useLayoutEffect will execute its response function and the last destroy function synchronously, which will block DOM rendering.

useEffect

commitBeforeMutationEffects

In this stage, useEffect focuses on experiencing a sentence as follows:

function commitBeforeMutationEffects() {
  while (nextEffect$1 !== null) {
    // A series of assignment operations are omitted. The flags here should be taken from the flags corresponding to the effect of FunctionComponent. For specific implementation, please refer to the source code.    var flags = ;

  // Handle life cycle    if ((flags & Snapshot) !== NoFlags) {
      setCurrentFiber(nextEffect$1);
      commitBeforeMutationLifeCycles(current, nextEffect$1);
      resetCurrentFiber();
    }

 // This if is judged that only useEffect is true, useLayoutEffect is false    if ((flags & Passive) !== NoFlags) {
      // If there are passive effects, schedule a callback to flush at
      // the earliest opportunity.
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
 // This is the reason why useEffect is asynchronous. After the DOM operation, React will schedule flushPassiveEffects        scheduleCallback(NormalPriority, function () {
          flushPassiveEffects();
          return null;
        });
      }
    }

    nextEffect$1 = nextEffect$;
  }
}

commitMutationEffects

In this stage, React will perform a series of DOM node updates, and then execute a method: commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);

Then a Functional Component with useEffect does not conform to the judgment logic of unmount at this stage, so the unmount operation will not be performed in this place.

commitLayoutEffects

In this stage, there is still a very important method: commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);

This if judgment is the same as the if judgment in the previous stage, useEffec will not do any operation in this judgment.

Follow-up stage

After completing commitLayoutEffects, there is another operation:

if (rootDoesHavePassiveEffects) {
    // This commit has passive effects. Stash a reference to them. But don't
    // schedule a callback until after flushing layout work.
    rootDoesHavePassiveEffects = false;
    rootWithPendingPassiveEffects = root;
    pendingPassiveEffectsLanes = lanes;
    pendingPassiveEffectsRenderPriority = renderPriorityLevel;
  }

That is, rootWithPendingPassiveEffects is set to root. The reason for this is related to the next flushPassiveEffects asynchronous scheduling registered in the first stage of commitBeforeMutationEffects. Let's look at the following implementation of flushPassiveEffects:

function flushPassiveEffectsImpl() {
 if (rootWithPendingPassiveEffects === null) {
    return false;
  }
 // Omit a series of performance tracking and other operations commitPassiveUnmountEffects();
  commitPassiveMountEffects(root, );
}


As can be seen from the above code segment, the dispatch callback registered by useEffect in the first phase will perform unmount and mount operations after the page is updated. It is worth mentioning that the registration time of effect in this callback is in the commitLayoutEffects stage.

useLayoutEffect

In fact, according to our analysis of useEffect, in the respective if judgments in the commitMutationEffects and commitLayoutEffects stages, useLayoutEffect is judged by if. Therefore, in the commitMutationEffect stage, the last destroy function of useLayoutEffect is synchronized, and in the commitLayoutEffect stage, the execution function of useLayoutEffect is synchronized, and the execution function of useLayoutEffect is synchronized, and the destruction function is registered.

in conclusion

At this point, we roughly looked at the code in the commit stage and analyzed the following why useEffect is executed asynchronously, while useLayoutEffect is executed synchronously. I have not posted the specific code too much in the article because these are all variable. The real process overview and the mental model designed by the React team for this mechanism require us to slowly become familiar with it while constantly debugging the code and understanding.

What I am interested in in the implementation of hooks later, the more critical useReducer will focus on the source code to see if you can write a simple version and put it into the Alipay mini program to implement a custom Alipay hooks for daily productivity development.

This is the end of this article about the difference between useEffect and useLayoutEffect in React. For more related React useEffect useLayoutEffect content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!