Let’s first take a look at the official document of React to introduce these two hooks to establish an overall understanding

useEffect(create, deps):

The Hook receives a function that contains imperative code that may have side effects. Changing the DOM, adding subscriptions, setting timers, logging, and performing other side effects within the body of a function component (in this case, during the React rendering phase) are not allowed, as this can cause unexplained bugs and break UI consistency. Use useEffect for side effects. The function assigned to useEffect is executed after the component is rendered to the screen. You can think of Effect as an escape route from the purely functional world of React to the imperative world.

useLayoutEffect(create, deps):

Its function signature is the same as useEffect, but it calls Effect synchronously after all DOM changes. You can use it to read DOM layouts and trigger rerenders synchronously. The update schedule inside useLayoutEffect is refreshed synchronously before the browser performs the drawing.

Notice the fields in bold. The React official documentation specifies the execution timing of the two hooks. Let’s dive into the React execution process to understand

The problem

  • UseEffect and useLayoutEffect?
  • UseEffect or useLayoutEffect which is equivalent to componentDidMount, componentDidUpdate?
  • UseEffect or useLayoutEffect which is equivalent to componentWillUnmount?
  • Why is it recommended to put DOM modification operations in useEffect instead of useEffect?

process

  1. React, after diff, enters the COMMIT phase, which maps changes to the virtual DOM to the real DOM

  2. Early in the COMMIT phase, some lifecycle methods are called. For class components, the getSnapshotBeforeUpdate lifecycle of the component needs to be triggered, and for function components, the useEffect create Destroy function is scheduled

  3. Note that this is scheduling, not execution. In this phase, life cycle functions generated by using the useEffect component are added to the schedule queue maintained by React itself. These life cycle functions are given a normal priority and executed asynchronously

// React does this, but the actual process is much more complicated

setTimeout((a)= > {
      const preDestory = element.destroy;
      if(! preDestory) prevDestroy();const destroy = create();
      element.destroy= destroy;
}, 0);
Copy the code
  1. Then, React sets the virtual DOM to the real DOM. The commitWork function is called. The commitWork function calls different DOM modification methods for different Fiber nodes. For example, text nodes and element nodes are modified differently.

  2. If the commitWork encounters a Fiber node of the class component, it does nothing but return to finish the work and proceed to the next node. This is easy to understand. The Fiber node of the class component does not have a corresponding real DOM structure, so there is no related operation

  3. In this phase, the function component synchronously calls the destroy function returned from the last render useLayoutEffect(create, deps)

  4. Note that a node is after the commitWokr, at which point we have mapped the changes to the real DOM

  5. But because the JS thread and the browser rendering thread are mutually exclusive, and because the JS virtual machine is still running, even if the real DOM in memory has changed, the browser does not immediately render to the screen

  6. At this point, the finishing touches are done, and the corresponding lifecycle methods are executed synchronously, so what we call componentDidMount, ComponentDidUpdate and the Create function of useLayoutEffect(CREATE, DEPS) are executed synchronously at this stage.

  7. For React, the commit phase is uninterruptible. All nodes that need to be committed are committed at once. At this point, the REACT update is complete and JS execution stops

  8. The browser renders the changed DOM onto the screen, and react updates all the DOM nodes that need to be updated with a single backflow and redraw

  9. After the browser renders, the browser notifies React that it is idle. React starts to execute tasks in its scheduling queue, and then useEffect(CREATE, DEPS) is generated

answer

UseEffect and useLayoutEffect?

UseEffect is executed asynchronously at rendering time and is not executed until the browser renders all changes to the screen.

UseLayoutEffect is executed synchronously at render time, and its execution timing is the same as componentDidMount, componentDidUpdate

For useEffect and useLayoutEffect which is equivalent to componentDidMount, componentDidUpdate?

ComponentDidMount; componentDidUpdate; componentDidMount; componentDidUpdate; Both are called synchronously by React and block browser rendering.

UseEffect or useLayoutEffect which is equivalent to componentWillUnmount?

As above, the detroy function of useLayoutEffect is called in the same position and timing as componentWillUnmount, and is called synchronously. The useEffect Detroy function is more like componentDidUnmount in terms of call timing (note that React does not have this lifecycle function).

Why is it recommended to put DOM modification operations in useEffect instead of useEffect?

You can see that during process 9/10, the DOM has been modified, but the browser rendering thread is still blocked, so no backflow, redraw process has occurred. Since the DOM in memory has been modified, useLayoutEffect can be used to retrieve the latest DOM node and make style changes to the DOM at this point, assuming that the height of the element is changed, These changes will be rendered to the screen at once in Step 11 along with the react changes, again at the cost of a single backflow and redraw.

If placed in useEffect, the useEffect function will be executed after the component has been rendered to the screen, and DOM modification will trigger the browser to backflow and redraw again, increasing the performance loss.