What are hooks?

Hooks are new in Act 16.8. They let you use state and other React functionality without having to write classes

Hooks are functions. There are several types of Hooks, each providing a channel for function Components to use React state and declaration cycles. React provides a number of predefined Hooks for use. Here are some examples:

useState

Let’s look at a piece of code

import React, { useState } from 'react'; Const [count, setCount] = useState(0); const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }Copy the code

You can see that useState has only one entry, which is the initial value of state. The initial value can be a number, string, array, or object, or even a function. When the input parameter is a function, the function is executed only when the component initializes the render:

const [state,setState] = useState(()=>{
    const initalState = someExpensiveComputation(props)
    return initalState
})
Copy the code

When we need to calculate the current state value from the previous state value, we need to pass in a function. This is similar to setState in class Component, and it is similar to setState in class Component. When the new value passed is the same as the previous value (compared using object.is), no update is triggered.

useEffect

Before explaining useEffect, let’s understand what side effects are. Network requests, subscribing to a module, or DOM operations are examples of side effects that useEffect is specifically designed to deal with. In normal cases, it is not recommended to write side effects code in function Component functions, which is prone to bugs.

In the following class Component example, the side effect code is written in componentDidMount and componentDidUpdate:

class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); }}Copy the code

UseEffect () {componentDidMount (); useEffect () {useEffect ();

import React, {useState, useEffect} from "react";

function FriendStatus(props) {

    const [count,setCount] = useState(0);
    
    useEffect(()=>{
        document.title = `You clicked $(count) times`
    });
    
    return (
        <div>
            <p>You clicked {count} times</p>
            <button @click={() => setCount(count + 1)}>
                Click me
            </button>
        </div>
    )
}
Copy the code

UseEffect is executed after every DOM rendering, without blocking the page rendering. UseEffect also has the execution timing for componentDidMount, componentDidUpdate, and omponentWillUnmount.

There are also side effects that require additional cleanup when a component is uninstalled, such as subscribing to a feature:

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}
Copy the code

After subscribing to componentDidMount, we need to unsubscribe from componentWillUnmount. Next we use useEffect in the function component:

import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // Return a function for additional cleanup: return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading... '; } return isOnline ? 'Online' : 'Offline'; }Copy the code

When useEffect returns a function, React performs a cleanup before the next execution of the side effect. The entire component declaration cycle flow can be understood as follows:

Component mount -> Perform side effects -> Component update -> Perform cleanup function -> Perform side effects -> Component Update -> Perform cleanup function -> Component unload

As mentioned above, useEffect is executed after each render, but sometimes we only want it to be executed when the state and props change

Use in class Component like this:

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}
Copy the code

With useEffect, we just pass in the second argument:

useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // Effect is executed only when count changesCopy the code

When the second argument is an array, you can pass in multiple values, usually passing in all the props and state you need

When side effects only need to be performed at componentDidMount and componentWillUNmount, the second argument can be passed as an empty array []. The implementation benefits are similar to componentDidMount and componentWillUNmount

useLayoutEffect

UseLayoutEffect is used exactly the same as useEffect in that both side effects and cleanups can be performed, with the only difference being when they are performed

UseEffect does not block the browser’s drawing task; it does not execute until the page is updated.

UseLayoutEffect, like componentDidMount and componentDidUpdate, blocks the rendering of a page and causes it to stall if it executes a time-consuming task.

In most cases, useEffect is a good choice. The only exception to this rule is when DOM manipulation is required based on the new UI. UseLayoutEffect ensures that it is performed before rendering the page, i.e. rendering the page to its final effect. If useEffect is used, the page is more likely to jitter after rendering twice.

useContext

UseContext allows us to share variables directly across component hierarchies

The following code allows the count variable to be passed and used across hierarchies (that is, the context is implemented), and the child component changes when the parent component’s count changes:

import React, { useState , createContext } from 'react';

const CountContext = createContext()

function Example(){

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

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>click me</button>
    
            <CountContext.Provider value={count}>
            </CountContext.Provider>

        </div>
    )
}
export default Example;
Copy the code

How does a hook component receive this variable

import React, {useState , createContext , useContext} from 'react'; Function Counter(){const count = useContext(createContext) return (<h1>{count}</h1>)}Copy the code

Provider < countContext. Provider> < countContext. Provider> < countContext. Provider>

<CountContext.Provider value={count}>
    <Counter />
</CountContext.Provider>
Copy the code

useMemo

Let’s start with an example that doesn’t use useMemo:

import React from 'react'

export default function withoutMemo() {
    const [count , setCount] = useState(1);
    const [val , setValue] = useState('');
    
    function expensive() {
        let sum = 0;
        for (let i = 0; i < count * 100; i++){
            sum += i;
        }
        return sum;
    }
    
    return (
        <div>
            <h3>{count} - {val} - {expensive()}</h3>
            <div>
                <button @click={() => setCount(count + 1)} > +c1 </button>
                <input value={val} onChange={event => setValue(event.target.value)} />
            </div>
        </div>
    )
}
Copy the code

Here we create two states, and then we do an expensive calculation using the expensive function to get some value of count, and we can see: Rerendering of either count or val triggers the execution of the expensive component, but the calculation only depends on the value of count. There is no need to re-evaluate val when it changes. In this case, we can use useMemo as follows:

import React from 'react'

export default function withoutMemo() {
    const [count , setCount] = useState(1);
    const [val , setValue] = useState('');
    const expensive = useMemo(() => {
        let sum = 0;
        for (let i = 0; i < count * 100; i++){
            sum += i;
        }
        return sum;
    },[count]);
    
    return (
        <div>
            <h3>{count} - {expensive()}</h3>
            {val}
            <div>
                <button @click={() => setCount(count + 1)} > +c1 </button>
                <input value={val} onChange={event => setValue(event.target.value)} />
            </div>
        </div>
    )
}
Copy the code

As you can see above, useMemo is used to perform expensive calculations and then return the calculated value, passing count in as a dependent value. This only triggers the expensive execution when count changes, and returns the last cached value when val is modified.

useCallback

With useMemo out of the way, useCallback is next. UseCallback is similar to useMemo, but it returns cached functions. Let’s look at the simplest use:

const fnA = useCallback(fnB, [a])
Copy the code

The useCallback above will return the function fnB we passed to it and cache the result; When a dependency changes, the new function is returned. Since the returned function is a function, we can not well determine whether the returned function is changed, so we can use the new data type Set of ES6 to determine, as follows:

import React, { useState , useCallback } from 'react' const set = new set(); export default function Callback() { const [count , setCount] = useState(1); const [val , setValue] = useState(''); const callback = useCallback(() => { console.log(count) },[count]); set.add(callback) return ( <div> <h3>{count}</h3> <h3>{set.size}</h3> <div> <button @click={() => setCount(count + 1)} >  +c1 </button> <input value={val} onChange={event => setValue(event.target.value)} /> </div> </div> ) }Copy the code

We can see that set.size is +1 each time we change count. This means that useCallback depends on the variable count. When val changes, set.size does not change, indicating that the old cached version of the function is returned.

What does it do to know what useCallback is?

The usage scenarios are as follows: there is a parent component that contains a child component that receives a function as props; In general, if the parent component updates, the child component also performs the update; In most cases, however, the update is not necessary. We can use useCallback to return a function, and then pass that function as props to the child component; In this way, the child components can avoid unnecessary updates.

import React, { useState , useCallback , useEffect } from 'react'

function Parent() {
    const [count , setCount] = useState(1);
    const [val , setValue] = useState('');
    
    const callback = useCallback(() => {
        return count;
    },[count]);
    
    set.add(callback)
    
    return (
        <div>
            <h3>{count}</h3>
            <Child callback={callback} />
            <div>
                <button @click={() => setCount(count + 1)} > +c1 </button>
                <input value={val} onChange={event => setValue(event.target.value)} />
            </div>
        </div>
    )
}

function Child({ callback }) {
    const [count , setCount] = useState(() => callback());
    useEffect(() => {
        setCount(callback());
    },[callback]);
    
    return <div> {count} </div>
}
Copy the code

In addition to the examples above, all functions that rely on local state or props to create functions that need to be cached are useCallback scenarios.

useRef

UseRef returns a normal JS object that can store arbitrary data in the current property, just as it did with this, which instantiates the object.

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

  const prevCountRef = useRef();

  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return <h1>Now: {count}, before: {prevCount}</h1>;
}
Copy the code

Custom Hooks

A custom Hook is a normal function definition. It is named use to facilitate static code detection.

conclusion

That’s it, Hooks. It takes a lot of effort to understand the Hooks design completely, so hopefully this article has provided you with some help learning about this new feature.