I. The execution process of useEffect and useLayoutEffect

The first thing to note is that useLayoutEffect and useEffect are very similar and have the same function signature. The only difference is that useEffect is executed asynchronously, while useLayoutEffect is executed synchronously. When the function component is refreshed (rendered), the whole process of the component containing the useEffect runs as follows.

  1. component re-rendering is triggered (by changing the component state or the component’s parent component re-rendering, causing the child nodes to render).
  2. the component function is executed.
  3. the component is rendered and rendered to the screen.
  4. the useEffect hook is executed.

When the function component is refreshed (rendered), the entire operation of the component containing the useLayoutEffect is as follows.

  1. component re-rendering is triggered (by changing the component state or the component’s parent component re-rendering, causing the child component to render).
  2. the component function is executed.
  3. useLayoutEffect hook is executed, and React waits for the useLayoutEffect function to finish executing.
  4. The component is rendered and rendered to the screen.

The advantage of usingEffect asynchronously is that react renders the component without having to wait for the useEffect function to finish executing, causing blocking.

99% of the cases, using useEffect is fine. The only case where we need to use useLayoutEffect is that in the case of useEffect, our screen will flicker (the component is rendered twice in a short period of time).

For example, in the code below, the component renders twice.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import ReactDom from "react-dom";
import React, {useEffect, useLayoutEffect, useState} from "react";
const App = () => {
    const [value, setValue] = useState(0);
    useEffect(() => {
        if (value === 0) {
            setValue(10 + Math.random() * 200);
        }
    }, [value]);
    console.log("render", value);
    return (
        <div onClick={() => setValue(0)}>
            value: {value}
        </div>
    );
};
ReactDom.render(
    <App />,
    document.getElementById("root")
);

In the above code, if you use useEffect, when you click on the div, there will be a short flicker and the screen will appear first with 0 and then with the number set in useEffect, but with useLayoutEffect there will be no flicker.

II. Using useRef to get the previous state of the component

After understanding the execution process of useEffect, we can use useRef to get the state and props of the previous state of the component, please see the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function Counter() {
    const [count, setCount] = useState(0);
    const prevCountRef = useRef<number>(1); // 组件前一个状态
    // useEffect会在组件函数执行后,在执行
    useEffect(() => {
        prevCountRef.current = count;
    });

    // 这一句会在useEffect函数执行前执行
    const prevCount = prevCountRef.current;
    return (
        <div>
            <h1>Now: {count}, before: {prevCount}</h1>
            <button onClick={() => setCount(count => count + 1)}>click</button>
        </div>
    );
}

Since useEffect will be executed after the component function is executed, preCountRef will store the previous value.