This is the first article in the React Advanced series, which will cover some new knowledge and principles of React for those interested.

Note: Hooks were only officially released in React 16.8

Why Hooks

Component nesting problem

Before, if we needed to pull out some repetitive logic, we used HOC or render props. But by implementing components in this way, you open React DevTools and see that components are wrapped in various other components. This makes debugging more difficult in the first place, and it also makes it harder to share state.

However, by using Hooks to remove repeat logic, there is no nesting of components, and state sharing is possible.

Class component issues

If we need a component to manage state, we must create one using class. But once class components become complex, scattered code becomes difficult to maintain. In addition, class components compile much more code from Babel than function components.

Hooks allow us to manage state as a function component, and to write discrete business logic into Hooks that are easy to reuse and maintain.

How do you use the Hooks

Having said something about the benefits of Hooks, let’s get down to business and learn a few Hooks that are commonly used by implementing a counter.

useState

The use of useState is simple, passing in an initial state, returning a state, and a function to modify the state.

// useState returns a constant state
// Each time the component is re-rendered, the current state is different from the previous state
// Even if the state is an object
const [count, setCount] = useState(1)
Copy the code

SetCount is used the same way as setState, passing in a new state or function.

setCount(2)
setCount(prevCount= > prevCount + 1)
Copy the code

UseState is easy to use. If we needed to implement a counter, we would have to write it in class, but now we can do this in the form of the function component + Hooks.

function Counter() {
  const [count, setCount] = React.useState(0)
  return (
    <div>
      Count: {count}
      <button onClick={()= > setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={()= > setCount(prevCount => prevCount - 1)}>-</button>
    </div>
  );
}
Copy the code

useEffect

Now our timer needs to be updated to print the current count after the component is updated, which we can do with useEffect

function Counter() {
  const [count, setCount] = React.useState(0)
  
  React.useEffect((a)= > {
    console.log(count)
  })
  
  return (
    <div>
      Count: {count}
      <button onClick={()= > setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={()= > setCount(prevCount => prevCount - 1)}>-</button>
    </div>
  );
}
Copy the code

The above code will print the correct count when we change the count. We can basically think of useEffect as componentDidUpdate. The difference between them will be seen in the next example.

UseEffect can also return a function similar to componentWillUnmount

function Counter() {
  const [count, setCount] = React.useState(0)
  
  React.useEffect((a)= > {
    console.log(count)
    return (a)= > console.log('clean', count)
  })
  
  // ...
}
Copy the code

Every time we update the count, we print the clean line of log first

Now our requirements are upgraded again, requiring us to print the count after a delay of two seconds after the counter is updated. This is as simple as reworking the code inside useEffect

React.useEffect((a)= > {
    setTimeout((a)= > {
        console.log(count)
    }, 2000)})Copy the code

When we quickly click the button, we can see the correct count after a two-second delay. But if we write this code into componentDidUpdate, things change.

componentDidUpdate() {
    setTimeout((a)= > {
        console.log(this.state.count)
    }, 2000)}Copy the code

For this code, if we click the button quickly, you will see the same counts printed after a delay of two seconds. This is because in useEffect we caught the correct count every time by means of closures. But in componentDidUpdate, the this.state.count method only gets the latest state, because it’s an object.

Of course, if you just want to get the latest state, you can use useRef to do this.

function Counter() {
  const [count, setCount] = React.useState(0)
  const ref = React.useRef(count)
  
  React.useEffect((a)= > {
    ref.current = count
    setTimeout((a)= > {
        console.log(ref.current)
    }, 2000)})/ /...
}
Copy the code

UseRef can be used to store any value that changes, eliminating the ability to store data by instance on a function component. In addition, you can ask useRef for the data before the change.

function Counter() { const [count, SetCount] = react.usestate (0) const ref = react.useref () react.useeffect (() => {setCount] = react.usestate (0) const ref = react.useref () react.useeffect (() => ref.current = count }) <div> Count: {count} PreCount: {ref.current} <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> </div> //... }Copy the code

Now that we need to upgrade again, we need to get the initial count through the interface, and we simulate this behavior with setTimeout.

function Counter() {
  const [count, setCount] = React.useState();
  const [loading, setLoading] = React.useState(true);

  React.useEffect((a)= > {
    setLoading(true);
    setTimeout((a)= > {
      setCount(1);
      setLoading(false);
    }, 2000);
  });
  return (
    <div>{! loading ? (<div>
          Count: {count}
          <button onClick={()= > setCount(pre => pre + 1)}>+</button>
          <button onClick={()= > setCount(pre => pre - 1)}>-</button>
        </div>
      ) : (
        <div>loading</div>
      )}
    </div>
  );
}
Copy the code

If you execute this code, useEffect executes indefinitely. This is because a state update is triggered again inside useEffect, so useEffect is executed again.

We can solve this problem by using the second parameter of useEffect

React.useEffect((a)= > {
    setLoading(true);
    setTimeout((a)= > {
      setCount(1);
      setLoading(false);
    }, 2000); } []);Copy the code

The second argument is passed in an array of dependencies, and useEffect is triggered again only if the dependency properties change. In the example above, passing an empty array means that useEffect will only be executed once.

Now that our code is a little ugly, you can separate out this part of the requested code into a function, you might write something like this

const fetch = (a)= > {
    setLoading(true);
    setTimeout((a)= > {
      setCount(1);
      setLoading(false);
    }, 2000);
}

React.useEffect((a)= > {
    fetch()
}, [fetch]);
Copy the code

But the problem with this code is the same as in the beginning, and it will execute indefinitely. This is because although you pass in the dependency, the fetch is recreated every time the component is updated, so useEffect assumes that the dependency has been updated, so the callback is executed again.

To fix this we need to use a new Hooks useCallback. This Hooks will generate a callback that will not be created again as the component is updated, so let’s rewrite the code again using this Hooks

const fetch = React.useCallback((a)= > {
    setLoading(true);
    setTimeout((a)= > {
      setCount(1);
      setLoading(false);
    }, 2000);
}, [])

React.useEffect((a)= > {
    fetch()
}, [fetch]);
Copy the code

And there we are, with several Hooks + function components that do exactly what you would need a class component to do.

conclusion

We’ve learned a few Hooks that are commonly used from several counter requirements, so we’ll summarize this section.

  • UseState: Passes in the initial state we need, returns a constant state and a function that changes the state
  • UseEffect: The first argument takes a callback that will be executed each time the component is updated, and the callback can return a function that will be executed before each component destruction. ifuseEffectThe interior has properties that depend on the outside and wants the dependency properties to remain unchanged without repeated executionuseEffectYou can pass in a dependency array as the second argument
  • UseRef: If you need a place to store changing data
  • UseCallback: If you need a callback that will not be recreated with component updates

In addition, I have packaged several Hooks apis that are frequently used. If you are interested, read the code below. The code in the repository is constantly updated.

The last

We learned from this article how to use Hooks. If you have any more questions, feel free to let me know in the comments section.

All of my articles in the series will be updated first on Github if you are interested. This year, I will focus on the following three columns

  • Relearn JS
  • The React advanced
  • Rewrite the component

Finally, feel the content is helpful can pay attention to my public number “front-end really fun”, there will be a lot of good things waiting for you.