This article has participated in the call for good writing activities, click to view: back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!

The introduction

Hooks are new to React 16.8, two years in the making, and allow you to use state and other React features without writing a Class component. UseEffect is one of the basic Hooks that I use a lot in my projects but have some confusion, such as:

  • How to use it correctlyuseEffect
  • useEffectWhen to execute?
  • useEffectAnd the life cycle?

This paper mainly analyzes useEffect from the above aspects, as well as the use of another Hook useLayoutEffect which looks very similar to useEffect and the differences between them.

UseEffect profile

I’ll start with two concepts, pure functions and side effects.

  • Pure Function: FOR the same input, always get the same output without any observable side effects, such a Function is called Pure Function.
  • Side effect Function: a Function that has additional effects on the calling Function in addition to returning the value of the Function is called a Side effect Function.

UseEffect runs extra code after React updates the DOM, which performs side effects such as requesting data, setting up subscriptions, and manually changing the DOM in the React component.

UseEffect correctly

Basic usage: useEffect(effect)

UseEffect (effect) execution times and execution results vary according to the number and type of parameter transmission. The following describes useEffect.

  • By default,effectIt is executed after each render. The following is an example:
useEffect(() = > {
  const subscription = props.source.subscribe();
  return () = > {
    // Clear the subscription
    subscription.unsubscribe();
  };
});
Copy the code
  • You can also set the second parameter, an array of dependencies useEffect(effect,[])And let it execute when the value in the array changes, and you can set multiple dependencies in the array, and any one of them changes,effectWill be re-executed. The following is an example:
useEffect(
  () = > {
    const subscription = props.source.subscribe();
    return () = > {
      subscription.unsubscribe();
    };
  },
  [props.source],
);
Copy the code

Note that when a dependency is a reference, React will compare the memory address of the dependency to that of the dependency rendered last time. If the memory address is the same, effect will not be executed. Here is an example (click online Test) :

function Child(props) {
  
  useEffect(() = > {
    console.log("useEffect");
  }, [props.data]);
  
  return <div>{props.data.x}</div>;
}

let b = { x: 1 };

function Parent() {
  const [count, setCount] = useState(0);
  console.log("render");
  return (
    <div>
      <button
        onClick={()= > {
          b.x = b.x + 1;
          setCount(count + 1);
        }}
      >
        Click me
      </button>
      <Child data={b} />
    </div>
  );
}
Copy the code

The results are as follows:

In the example above, the dependency in the useEffect function in the component
is an object. The value in the object changes when the button is clicked, but the memory address passed to the
component does not change, so console.log(“useEffect”) is not executed. UseEffect will not be printed. To solve this problem, we can use properties in the object as dependencies, rather than the whole object. Modify the component
in the above example as follows:

function Child(props) {
  
  useEffect(() = > {
    console.log("useEffect");
  }, [props.data.x]);
  
  return <div>{props.data.x}</div>;
}
Copy the code

The modified result is as follows:

See useEffect function console.log(“useEffect”) is executed, printing useEffect.

  • When the dependency is an empty array [],effectExecute only on the first render.

UseEffect execution time

By default, effect is executed after the first rendering and after each update, or after only some values have changed. The emphasis is on delaying the call after each rendering round (asynchronous execution). This is the benefit of useEffect, which ensures that when executing effect, The DOM has been updated without blocking DOM rendering and causing visual congestion.

Difference between useEffect and useEffect

UseLayoutEffect is used in the same way as useEffect, except when it is executed.

As mentioned above, the content of effect is executed after rendering the DOM. However, not all actions can be delayed in effect. For example, if the browser needs to manipulate the DOM to change the page style before rendering the next time, a splash screen will occur if you useEffect. UseLayoutEffect is executed synchronously before the browser executes the drawing, so putting it in useLayoutEffect avoids this problem.

In this article, we can clearly see the specific implementation of the above example: the difference between useEffect and useEffect

Compare useEffect with lifecycle

If you’re familiar with life cycle functions, you might want to think of useEffect as a lifecycle analogy, but it’s not really recommended because the mental model of useEffect is different from other life cycles such as componentDidMount.

There is no lifecycle in the Function component. React synchronizes the DOM based on our current props and state, which is solidify for each render, including state, props, side Effects, and all functions written in the Function component.

Also, most useEffect functions do not execute synchronously and do not block the browser update screen as componentDidMount or componentDidUpdate does.

Therefore, useEffect can be considered as an independent function after each render, which can receive props and state, and the props and state are the data of the next render, which are independent. Whereas this.state in Life Cycle componentDidMount always points to the latest data, useEffect is not necessarily the latest data, but more like a part of the render result — each useEffect belongs to a specific render. The following is an example of comparison:

  • Used in the Function componentuseEffect ,Code example (Click online Test):
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() = > {
    setTimeout(() = > {
      console.log(`You clicked ${count} times`);
    }, 3000);
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()= > setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Copy the code

The results are as follows:

  • Example code for a usage lifecycle in a Class component is as follows:
 componentDidUpdate() {
    setTimeout(() = > {
      console.log(`You clicked The ${this.state.count} times`);
    }, 3000);
  }
Copy the code

The results are as follows:

But it’s not efficient to execute effect after every render. So what’s the solution? This requires us to tell React to compare dependencies to determine whether to execute effect.

How to bind dependencies exactly

React needs to be told what external variables are used in Effect. What happens if you don’t set dependencies correctly? The following is an example:

In the example above, useEffect will not be run again (useEffect is printed only once), but the count dependency is not declared in the unload dependency array. In effect, setInterVal takes count, which is always initialized to 0, and calls setCount(0 + 1) every second after it, which always results in 1. There are two ways to resolve dependencies correctly:

1. Include all values used in effect in the dependency array

Add count, the external variable used in effect, to the dependency array as it is:

You can see that the dependency array is correct and resolves the above problem, but you can also see that the ensuing problem is that the timer is cleared and reset after each count change, creating/destroying repeatedly, which is not what we want.

2. The second approach is to modify the code in Effect to reduce dependencies

Change the code inside effect to make useEffect less dependent, which requires some common techniques to remove dependencies, such as: SetCount also has a function callback mode. You don’t need to care what the current value is, just change the “old value”, so you don’t need to write count to the dependency array to tell React, because React already knows this, as shown in the following example:

Whether side effects need to be removed

If you just run some extra code after React updates the DOM, such as sending network requests, manually changing the DOM, and logging, you don’t need to clean it up because it can be ignored.

To prevent the memory leak cleanup function that will be called before the component is unloaded, you can use the useEffect return value to destroy the listeners registered with useEffect.

The cleanup function executes after a new render as shown in the following example (click online test) :

const Example = () = > {
  const [count, setCount] = useState(0);

  useEffect(() = > {
    console.log("useEffect");
    return () = > {
      console.log("return");
    };
  }, [count]);

  return (
    <div>
      <p>You Click {count} times </p>
      {console.log("dom")}
      <button
        onClick={()= > {
          setCount(count + 1);
        }}
      >
        Click me
      </button>
    </div>
  );
};
Copy the code

The results are as follows:

Note that the useEffect cleanup function is executed every time you re-render, not just when you uninstall the component.

Reference documentation

Dan’s Complete interpretation of useEffect — A Complete Guide to useEffect