directory

  • Description of function parameters
  • The difference between two hooks
  • A life-cycle function that replaces class
  • A trap for using props and state in effect
  • A thought on Hooks
  • reference

Description of function parameters

The two Effect hooks are Windows that React provides the user with logic to handle side effects, such as changing the DOM, adding subscriptions, setting timers, logging, and performing various other actions that are not allowed during rendering.

In use, the function signature of both hooks is the same:

useEffect(() = > {
  // Perform some side effects
  // ...
  return () = > {
    // Clean up the function}})Copy the code

This executes after every component update, kind of like componentDidUpdate, but don’t think of hooks in the same way as class component lifecycle thinking, it just seems to work that way first. If you want to execute only once, as componentDidMount does, the second argument is passed to an empty array:

useEffect(() = > {
  // Perform some side effects
  // ...
  return () = > {
    // Clean up the function}}, [])Copy the code

Sometimes, however, you need to conditionally execute effect depending on changes in props. To do this, you can pass useEffect a second argument, which is an array of values on which effect depends:

useEffect(
  () = > {
    const subscription = props.source.subscribe();
    return () = > {
      subscription.unsubscribe();
    };
  },
  [props.source],
);
Copy the code

At this point, the subscription is recreated only if the props. Source changes.

The difference between two hooks

Here comes the difference between useEffect and useEffect.

UseEffect only for most scenarios, and only try using useLayoutEffect when it doesn’t work.

But in what circumstances can useLayoutEffect make a difference?

In the React function, the JS thread and the render thread are mutually exclusive. In the React function, the update process is divided into following steps:

(This assumes the React component has been rendered successfully for the first time.)

  1. The user clicks on the event to change a state
  2. React updates the state variable internally
  3. React handles the DOM node returned from the update component.
  4. Draw the updated DOM data to the browser
  5. The user sees a new page

The first three steps are handled by React, that is, the JS thread executes the code we wrote in memory, while the fourth step actually hands the updated data to the renderer thread for processing.

UseEffect will only be invoked after step 4, after the browser has drawn, and will only be executed asynchronously. The asynchron is wrapped by React using requestIdleCallback, and will only be executed when the browser is idle. This ensures that the browser rendering process is not blocked.

UseLayoutEffect, on the other hand, executes between the third and fourth steps and blocks the subsequent flow synchronously.

The difference between the two can be seen in some DOM change scenarios:

The following code examples:

export default function FuncCom () {
    const [counter, setCounter] = useState(0);

    useEffect(() = > {
        if (counter === 12) {
            // To demonstrate, set a delay function synchronously to 500ms
            delay()
            setCounter(2)}});return (
        <div style={{
            fontSize: '100px'
        }}>
            <div onClick={()= > setCounter(12)}>{counter}</div>
        </div>)}Copy the code

As you can see, the initial screen is 0, and when setCounter is clicked, the screen is 12 and then 2:

Imagine that this is what happens in some animation scenes because setCounter(12) has already triggered a render when useEffect is executed. It’s not good in terms of experience.

When useLayoutEffect is replaced, only 0 and 2 will appear on the screen. This is due to the synchronization feature of useLayoutEffect, which synchronizes and updates DOM data before rendering by the browser. Even multiple operations will be processed once before rendering and then rendered to the browser. This will not cause the flash screen phenomenon.

Here’s a quick summary:

  • UseEffect is an asynchronous non-blocking call
  • UseLayoutEffect is a synchronous blocking call
  • UseEffect is drawn by the browser
  • UseLayoutEffect completes all operations after DOM changes (React updates) and before the browser draws

A life-cycle function that replaces class

Further, we want to replace the life cycle in the class component with the hook function in the function component, so how does this correspond?

Here’s another example of a class component:

class ClassCom extends React.Component {
    state = {
        value: 'a'
    }
    componentDidMount() {
        // Delay trigger
        delay()
        this.setState({
            value: 'fasd'})}componentDidUpdate() {
        if (this.state.value === 'b') {
            // Delay trigger
            delay()
            this.setState({
                value: 'c'}}})render() {
        return (
            <div
                onClick={()= > this.setState({
                    value: 'b'
                })}
            >
                Class Components {`${this.state.value}`}
            </div>)}}Copy the code

In the browser, the first rendering user will not see the value Class Components A, but the value after the mount state Class Components fasd. When the click event is triggered, Only values after didupDate Class Components C are displayed.

This indicates that componentDidMount and componentDidUpdate are both blocked synchronously, and before React is submitted to the browser rendering step.

UseLayoutEffect and componentDidMount/componentDidUpdate are called at the same time, and are called synchronously with React, blocking browser rendering.

UseLayoutEffect returns the clean function with the same location and timing as componentWillUnmount, and is called synchronously. UseEffect’s clean function is more like componentDidUnmount in terms of timing (although React doesn’t have this lifecycle function).

Although useLayoutEffect is more like a lifecycle function in class, the official recommendation is to use useEffect instead of useLayoutEffect in most normal cases because useEffect does not block rendering, Consider using useLayoutEffect only for scenarios involving DOM changes, animations, etc. All changes are updated to the browser at once to reduce user experience discomfort.

A trap for using props and state in effect

There is a stealth bug to be aware of when using effect.

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() = > {
    const id = setInterval(() = > {
      setCount(count + 1);
    }, 1000);
    return () = > clearInterval(id); } []);return <h1>{count}</h1>;
}
Copy the code

The intent of this code is simple: update count every 1000ms, but in reality, count will only ever increase to 1!

The same code implemented with the class component does not have this problem:

class Counter extends Components {
  state = {
    count: 0
  }
  id = null;
  componentDidMount() {
    this.id = setInterval(() = > {
      this.setState(({
        count: this.state.count + 1
      }));
    }, 1000);
  }
  componentWillUnmount() {
    clearInterval(this.id)
  }
  render() {
    return <h1>{this.state.count}</h1>}}Copy the code

The difference between the class component and the function component code above is that this.state in the class component is mutable! Each update is an update to the state object, and setInterval after interval references the value of the new state. This is common with class components, and we expect the same with state objects.

In functional components, however, things are different. The useEffect(callback) callback is completely new so that the state value referenced in it is bound only to the current render. Is that amazing? No, this is the closure! This is just a language feature of JavaScript.

useEffect(() = > {
    // The callback function is run only once, and count only remembers the value that was first rendered
    // So every setInterval will never change!
    const id = setInterval(() = > {
      setCount(count + 1);
    }, 1000);
    return () = > clearInterval(id); } []);Copy the code

Be careful with functional components. After writing class components, it is easy to misunderstand some of the uses of variables. Function components are treated as pure functions, and each time the component is updated to render the current page, it also remembers the variable values in the current environment. This is the logic and state synchronization advocated by the React Hooks. It’s a confusing contrast to the class component’s lifecycle thinking. It’s the same React, but it’s a new way of thinking, and I think it’s even closer to the nature of the JavaScript language, more functional.

Post: Making setInterval Declarative with React Hooks To resolve this confusion, take a look at post: Making setInterval Declarative with React Hooks

The solution is simple, but the thought process is amazing.

A thought on Hooks

React has promoted one-way data flow since its launch, updating components based on changes in the objects of state and props. This has revolutionized the front end, allowing developers to move away from the imperative mindset of jquery and embrace declarative programming.

Classic CalSS components are not without their problems, however. The complex lifecycle API breaks down our state logic into phases, which gives us a time dimension to think about when designing components.

Hooks are a further revolution, completely discarding the weight of time thinking, from thinking “what life cycle should my state logic be placed in” to thinking “what should my page look like as the state changes” and “what side effects should be triggered as the state changes”.

This “logical state and synchronization with the page” is the real React data flow way of thinking, which is a huge mental load reduction.

UseEffect and useLayoutEffect are fundamentally different from apis like componentDidMount, although they can substitute for impersonation. With the Effect Hook API, we are thinking about * what side effects do we need to do after the UI state is complete *? And in the componentMount API we’re thinking about what side effects can we do at this time?

ComponentMount API is thinking about the operation in each time phase, Effect Hook API does not need to consider the factor of time, only need to consider the processing of component state changes.

reference

  • How Are Function Components Different from Classes? — Overreacted
  • A Complete Guide to useEffect — Overreacted
  • Making setInterval Declarative with React Hooks — overreact reacted
  • When to useLayoutEffect Instead of useEffect (example)
  • UseEffect and useLayoutEffect difference _HSany330 column -CSDN blog _USelayouteffect
  • useEffect vs. useLayoutEffect in plain, approachable language
  • React Training: useEffect(fn, []) is not the new componentDidMount()