It is said that this hook can simulate the three life cycles of the class component

preface

It’s already been covered on the website, and it will be repeated here. UseEffect is a hook that executes side effects. The first argument is passed to a function that executes side effects and clears the last side effects after render. The return value of this function is the clear function. The second argument is an array of internal dependencies for executing the side effect function. When one of these dependencies is updated, the effect will generate a new side effect and execute the side effect. If there is no update, it will not be executed. If the second argument is not passed, then it does not indicate whether or not it is dependent, which means that the function component executes every time.

Obviously, the useEffect first argument can mimic didmount, didupDate, and its return value can mimic willunmount

Class Component lifecycle simulation

“Mimic the life cycle, useEffect second argument passes an empty array, no dependencies, only executes once, equivalent to didmount. If you want to distinguish between life cycles, if you do not pass the second parameter, it will run every time, equivalent to didupdate. I’m going to put a mount on it, and I’m going to put an if on it, and I’m going to simulate the life cycle.”

A lot of people think of this simulation, so let’s try it:

let mount;
function useForceUpdate() {
  const [_, forceUpdate] = useState(0);
  return (a)= > forceUpdate(x= > x + 1);
}

function UnmountTest() {
  useEffect((a)= > {
    if(! mount) { mount =true;
      console.log('did mount')}else {
      console.log('did update')}return (a)= > {
      mount = false;
      console.log('unmount')}})const forceUpdate = useForceUpdate();
  return (<div>I can be abandoned at any time<button onClick={forceUpdate}>Forced to update</button>
  </div>);
}

function State() {
  const [count, setCount] = useState(20);

  const handleCount = useCallback((a)= > {
    setCount(count= > count + 1)}, [])return (
    <div>
      {count}
      <button onClick={handleCount}>count+1</button>
      {(count % 2) && <UnmountTest />}
    </div>)}Copy the code

When count is odd, then show UnmountTest, and there is also a method in the component to update the component. By logic, useEffect does not pass the second argument, ensuring that it is executed every time. Then add a tag, indicating that the first time it was mounted. So let’s run a wave

  • Click count+1, show the component, print didmount
  • Click Count again, delete the component, print unmount

As expected, 😊

  • Click count+1, show the component, print didmount
  • Click force update, print unmount, didmount, click again, same thing

🤔️, what the hell, not meeting expectations

UseEffect is used to execute side effects. Each time you render, it will clear the previous side effect and execute the current side effect (if it has dependencies or does not pass in an array of dependencies)

In that case, every time it’s unmount, didmount, it does follow this logic, and it’s a little bit different from the analog life cycle that we’re assuming. In this case, we can solve the problem by splitting it into two useEffect calls:

function UnmountTest() {
  useEffect((a)= > {
      if (mount) {
          console.log('did update')}}); useEffect((a)= > {
      if(! mount) {console.log('did mount')
          mount = true;
      }
      return (a)= > {
          console.log('unmount')
          mount = false; }} []);const forceUpdate = useForceUpdate();
  return (<div>I can be abandoned at any time<button onClick={forceUpdate}>Forced to update</button>
  </div>);
}
Copy the code

This time, everything was as expected, ojbk😊

UseEffect & useLayoutEffect difference

UseEffect is asynchronous, useLayoutEffect is synchronous

Let’s take a look at the timing of a component from mount to rerender:

From left to right represents the timeline, the red is asynchronous, the red box is synchronous, and it runs from top to bottom. UseEffect is asynchronous, which uses requestIdleCallback to execute the incoming callback at idle time in the browser. For the most part, it doesn’t matter which one you use. If the side effects are long, such as a lot of computation, it will block the rendering if you use useLayoutEffect. This is just one case, we can look at this magic timer:

Click Start to start the timer, click Pause to pause. Click clear 0, pause and clear the number

function LYE() {
  const [lapse, setLapse] = React.useState(0)
  const [running, setRunning] = React.useState(false)

  useEffect(
    (a)= > {
      if (running) {
        const startTime = Date.now() - lapse
        const intervalId = setInterval((a)= > {
          setLapse(Date.now() - startTime)
        }, 2)
        console.log(intervalId)
        return (a)= > clearInterval(intervalId)
      }
    },
    [running],
  )

  function handleRunClick() {
    setRunning(r= >! r) }function handleClearClick() {
    setRunning(false)
    setLapse(0)}return (
    <div>
      <label>{lapse}ms</label>
      <button onClick={handleRunClick}>{running ? 'Pause' : 'Start '}</button>
      <button onClick={handleClearClick}>Pause and clear 0</button>
    </div>)}Copy the code

So, click clear zero actually not clear 0, just stopped, and the point is to continue to start. So we can just change it to useLayoutEffect, zero, zero and stop. In addition, when using useEffect, set interval to more than 16, the probability of success is 0, if more than that, the absolute clear. UseEffect is said to be asynchronous, so the problem is likely to be asynchronous here.

UseLayoutEffect is synchronized, so the process is exactly what we expected, and everything is under control. This is based on two points: useEffect’s interval delay is too small to clear the timing result, and useEffect’s interval delay is greater than 16. Starting from these two points, let’s comb the useEffect execution timing:

In this case, there is no clear timer result. Note the middle block: interval1 = render = clean useEffect1. Clean useEffect1 before running interval1 again, interval1 fires render to show the current timing result. For example, stop, setRunning(false), and setLapse(0) do run, but interval1 also sets the current timing, so setLapse(0) is useless.

Increase the interval delay

This is normal, obviously all within our expectations. After many tests, the delay critical point is 16ms.

Why is it 16ms?

RequestIdleCallback is a function that executes the callback when the browser is idle. Similar to requestAnimationFrame, except that requestIdleCallback has a lower priority. RequestAnimationFrame is an average of 60fps, and then 1000/60 is 16.66666, so each frame is spaced around 16ms. Finally, the source of the problem is exposed. If the interval is longer than one frame of screen time, the useEffect timer will not cause problems. Otherwise, the interval will execute one more time before useEffect.

If the article is helpful to you, Github, Nuggets can follow a wave of oh