Understand React Hooks and several commonly used hook functions

Write in front

React Hooks is a new mechanism launched by the react team in version 16.8 two years ago. As the most mainstream front-end framework, react's API is very stable. The release of this update shocked many front-end bosses who fear the new wheel. After all, each update is a high-cost learning. Is it easy to use?

The answer is easy to use. For React developers, there is only one more choice. In the past, the development method was based on Class components, while Hooks was based on function components, which means that these two development methods can coexist, and the new code can be implemented by Hooks according to the specific situation. This article mainly introduces the advantages of Hooks and several commonly used hook functions.

Hooks advantage

1. Deficiency of class components

  • Large number of codes:

    Compared with the writing method of function components, the amount of code using class components is slightly more, which is the most intuitive feeling.

  • this points to:

    Class components always need to consider the direction of this, while function components can be ignored.

  • Complex and difficult to maintain:

    In the higher version of React, some life cycle functions have been updated. Because these functions are decoupled from each other, it is easy to cause decentralized and decentralized writing, missing key logic and redundant logic, resulting in difficulties in later debugging. On the contrary, hooks can put the key logic together, which is not so fragmented, and it is easier to understand when debugging.

  • State logic is difficult to reuse:

    It is difficult to reuse state logic between components. render props or HOC may be used, but both render attributes and high-order components will wrap a layer of parent container (usually div elements) outside the original components, resulting in hierarchical redundancy.

2. Benefits of hooks

  • Logical reuse

    Reusing state logic before components often requires the help of complex design patterns such as high-level components. These high-level components will produce redundant component nodes, making debugging difficult. Next, use a demo to compare the two implementation methods.

Class

After the pro class is defined as the size of the next component, it is responsible for listening to the next component and passing it to the next component as the size of the next component.

const useWindowSize = Component => {
  // Generate a high-order component HOC, which only contains the logic of listening to the size of the window
  class HOC extends React.PureComponent {
    constructor(props) {
      super(props);
      this.state = {
        size: this.getSize()
      };
    }
    componentDidMount() {
      window.addEventListener("resize", this.handleResize); 
    }
    componentWillUnmount() {
      window.removeEventListener("resize", this.handleResize);
    }
    getSize() {
      return window.innerWidth > 1000 ? "large" : "small";
    }
    handleResize = ()=> {
      const currentSize = this.getSize();
      this.setState({
        size: this.getSize()
      });
    }
    render() {
      // Pass the window size to the real business logic component
      return <Component size={this.state.size} />;
    }
  }
  return HOC;
};

Next, you can call a function such as useWindowSize in the custom component to generate a new component with its own size attribute, for example:

class MyComponent extends React.Component{
  render() {
    const { size } = this.props;
    if (size === "small") return <SmallComponent />;
    else return <LargeComponent />;
  }
}
// Use useWindowSize to generate high-level components, which are used to generate the size attribute and pass it to the real business components
export default useWindowSize(MyComponent); 

Let's take a look at the implementation of Hooks

Hooks

const getSize = () => {
  return window.innerWidth > 1000 ? "large" : "small";
}
const useWindowSize = () => {
  const [size, setSize] = useState(getSize());
  useEffect(() => {
  const handler = () => {
      setSize(getSize())
    };
    window.addEventListener('resize', handler);
    return () => {
      window.removeEventListener('resize', handler);
    };
  }, []);
  
  return size;
};

use:

const Demo = () => {
  const size = useWindowSize();
  if (size === "small") return <SmallComponent />;
  else return <LargeComponent />;
};

From the above example, the window size is encapsulated by Hooks to turn it into a bindable data source. In this way, when the window size changes, the components using this Hook will be re rendered. Moreover, the code is also more concise and intuitive, which will not produce additional component nodes and will not appear so redundant.

  • Business codes are more aggregated

Here is an example of the most common timer.

class

let timer = null
componentDidMount() {
    timer = setInterval(() => {
        // ...
    }, 1000)
}
// ...
componentWillUnmount() {
    if (timer) clearInterval(timer)
}

Hooks

useEffect(() => {
    let timer = setInterval(() => {
        // ...
    }, 1000)
    return () => {
        if (timer) clearInterval(timer)
    }
}, [//...])

The implementation of Hooks can make the code more centralized and the logic clearer.

  • Concise writing

This is not an example. It can be understood from the literal meaning. Using function components can really reduce a lot of code. You can understand it all, hee hee~

The function and use of several built-in Hooks

Maintain the state of the function: let the component have the ability to use state

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

advantage:

Let the function component have the ability to maintain the state, that is, the state is shared between multiple renderings of a function component. Easy to maintain state.

Disadvantages:

Once the component has its own state, it means that if the component is re created, there needs to be a process of restoring the state, which usually makes the component more complex.

Usage:

  1. The parameter of useState(initialState) initialState is the initial value of creating state.

It can be any type, such as number, object, array, etc.

  1. The return value of useState() is an array with two elements. The first array element is used to read the value of state, and the second is used to set the value of this state.

Note here that the variable of state (count in the example) is read-only, so we must set its value through the second array element setCount.

  1. If we want to create multiple states, we need to call useState multiple times.

What values should be saved in state?

Generally speaking, a principle we should follow is: do not save the calculated value in state.

  • The value passed from props. Sometimes the value passed by props cannot be used directly, but must be displayed on the UI after certain calculation, such as sorting. So what we need to do is to reorder every time we use it, or use some cache mechanism instead of putting the results directly into the state.
  • The value read from the URL. For example, sometimes you need to read the parameter in the URL and take it as a part of the state of the component. Then we can read it from the URL every time we need it, instead of directly putting it into the state.
  • Value read from cookie, localStorage. Generally speaking, it is also read directly every time you need it, rather than put it in the state after reading it.

useEffect: executive side effect

useEffect(fn, deps);

useEffect, as the name suggests, is used to perform a side effect.

What are side effects?

Generally speaking, a side effect is a piece of code that has nothing to do with the current execution result. For example, to modify a variable outside the function, to initiate a request, and so on.

In other words, during the current execution of the function component, the execution of the code in useEffect does not affect the rendered UI.

Corresponding to Class components, useEffect covers three life cycle methods: ComponentDidMount, componentDidUpdate and componentWillUnmount. However, if you are used to using Class components, do not follow the method of mapping useEffect to one or several life cycles. You just need to remember that useEffect is to judge the dependency and execute it after each component render.

useEffect also has two special uses: no dependencies and dependencies as an empty array. Let's analyze it in detail.

  1. If there are no dependencies, it will be re executed after each render. For example:
useEffect(() => {
  // It must be executed after each render
  console.log('Render...........');
});
  1. If an empty array is used as a dependency, it will only be triggered during the first execution. The corresponding Class component is componentDidMount. For example:
useEffect(() => {
  // The component is executed when it is rendered for the first time, which is equivalent to componentDidMount in the class component
  console.log('did mount........');
}, []);

Summary usage:

To sum up, useEffect enables us to execute a callback function at the following four times to produce side effects:

  1. Execute after each render: the second dependency parameter is not provided.

For example, useeffect (() = > {}).

  1. Execute only after the first render: provide an empty array as a dependency.

For example, useeffect (() = > {}, []).

  1. Execute for the first time and after dependency changes: provide dependency array.

For example, useeffect (() = > {}, [DEPs]).

  1. Execute after unmount: return a callback function.

For example, useeffect() = > {return() = > {}, [].

useCallback: cache callback function

useCallback(fn, deps)

Why use useCallback?

In the React function component, every UI change is completed by re executing the whole function, which is very different from the traditional Class component: there is no direct way in the function component to maintain a state between multiple renderings.

function Counter() {
  const [count, setCount] = useState(0);
  const handleIncrement = () => setCount(count+1);
  return <button onClick={handleIncrement}>+</button>
}

Think about the process. Every time the component state changes, the function component will actually execute again. At each execution, a new event handler handleIncrement is actually created.

This also means that even if the count does not change, but the function component is re rendered due to other state changes (the function component is re executed), this writing method will create a new function each time. Creating a new event handler does not affect the correctness of the result, but it is actually unnecessary. This not only increases the overhead of the system, but more importantly: each time a new function is created, the component receiving the event handler function needs to be re rendered.

For example, the button component in this example receives handleIncrement as an attribute. If it is a new one every time, the React will think that the props of this component has changed, so it must be re rendered. Therefore, what we need to do is: only when the count changes, we need to redefine a callback function. And this is the role of useCallback Hook.

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

function Counter() {
  const [count, setCount] = useState(0);
  const handleIncrement = useCallback(
    () => setCount(count + 1),
    [count], // The callback function is recreated only when the count changes
  );
  return <button onClick={handleIncrement}>+</button>
}

useMemo: cache the result of calculation

useMemo(fn, deps);

useCallback(fn, deps) is equivalent to usememo (() = > FN, DEPs).

fn here is a calculation function to generate the required data. Generally speaking, fn will use some variables declared in deps to generate a result to render the final UI.

This scenario should be easy to understand: if a data is calculated from other data, it should be recalculated only when the data used, that is, the dependent data, changes.

Avoid double counting

Using useMemo Hook, you can avoid repeated calculation when the data used has not changed. Although the example shows a very simple scenario, if it is a complex calculation, it will be very helpful to improve the performance.

for instance:

const calc = (a, b) => {
    // Let's assume that the complex calculation is done here, and the power simulation is used for the time being
    return a ** b;
}
const MyComponent = (props) => {
    const {a, b} = props;
    const c = calc(a, b);
    return <div>c: {c}</div>;
}

If the calc calculation takes 1000ms, how to optimize it if you have to wait so long for each rendering?

a. When the value of B remains unchanged, the result of c is the same.

So we can use useMemo to cache the values to avoid double counting the same results.

const calc = (a, b) => {
    // Let's assume that the complex calculation is done here, and the power simulation is used for the time being
    return a ** b;
}
const MyComponent = (props) => {
    const {a, b} = props;
    // cache
    const c = React.useMemo(() => calc(a, b), [a, b]);
    return <div>c: {c}</div>;
}

The function of useCallback can be realized by useMemo:

 const myEventHandler = useMemo(() => {
   // Returns a function as the cache result
   return () => {
     // Event handling here
   }
 }, [dep1, dep2]);

To summarize:

I feel this way. In fact, hook establishes a relationship that binds a result to dependent data. Only when the dependency changes does the result need to be retrieved.

useRef: sharing data between multiple renderings

const myRefContainer =useRef(initialValue);

We can think of useRef as a container space created outside the function component. On this container, we can set a value through the unique current attribute, so as to share this value among multiple renderings of function components.

Important functions of useRef

1. Store data across renderings

The data saved with useRef is generally irrelevant to UI rendering. Therefore, when the value of ref changes, it will not trigger the re rendering of components, which is also the difference between useRef and useState.

give an example:

 const [time, setTime] = useState(0);
 // Define a container such as timer, which is used to save a variable between cross component rendering 
 const timer = useRef(null);

  const handleStart = useCallback(() => {
    // Use the value of the current property ref
    timer.current = window.setInterval(() => { setTime((time) => time + 1); }, 100);
  }, []);

2. Save the reference of a DOM node

In some scenes, we must obtain the reference of the real DOM node, so combined with the ref attribute of React and the Hook of useRef, we can obtain the real DOM node and operate on it.

Official example of React:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // The current attribute points to the real input DOM node, so you can call the focus method
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

understand:

You can see that the attribute ref provides the ability to obtain the DOM node, and uses useRef to save the application of this node. In this way, once the input node is rendered to the interface, we use inputel Current can access instances of real DOM nodes

useContext: defines the global state

Why use useContext?

There is only one way to transfer state between React components, that is, through props. Disadvantages: this transfer relationship can only be carried out between parent and child components.

Then the problem arises: how to share data between components across levels or at the same level? This involves a new proposition: global state management.

The solution provided by react: Context mechanism.

Specific principle:

React provides a mechanism such as Context, which enables all users to create a Context on the component tree where a component starts. In this way, all components in the component tree can access and modify the Context.

Then in the function component, we can use a Hook such as useContext to manage the Context.

Use: (official example is used here)

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};
// Create a Context of Theme

const ThemeContext = React.createContext(themes.light);
function App() {
  // The whole application uses themecontext Provider as root component
  return (
    // Use themes Dark as the current Context 
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// Use a Button in the Toolbar component that will use Theme
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

// Use useContext in the Theme Button to get the current theme
function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{
      background: theme.background,
      color: theme.foreground
    }}>
      I am styled by theme context!
    </button>
  );
}

advantage:

Context provides a convenient mechanism for sharing data among multiple components.

Disadvantages:

Context is equivalent to providing a mechanism for defining global variables in the React world, and global variables mean two points:

1. It will make debugging difficult because it is difficult for you to track how a Context change is generated.

2. It is difficult to reuse components, because if a component uses a Context, it must ensure that the Provider of the Context must be on the path of its parent component where it is used.

Practical application scenario

Due to the above shortcomings, in the development of React, we rarely use Context to share too much data except for variables that need to be set globally at a glance, such as Theme and Language. It should be emphasized that Context provides a powerful mechanism to enable React applications to define global responsive data.

In addition, many state management frameworks, such as Redux, use the mechanism of Context to provide a more controllable state management mechanism between components. Therefore, understanding the mechanism of Context also allows us to better understand the principle of the implementation of frameworks such as redux.

last

I don't think there is much content this time. In fact, after learning the two core Hooks of useState and useEffect, I can basically complete the development of most React functions.

useCallback, useMemo, useRef and useContext. These hooks are designed to solve specific problems encountered in function components.

There are several more marginal hook s that are no longer written here. Interested bosses can move to the official documents.

Code words are not easy, but also hard. Leaders guide communication~

team

TNTWeb - Tencent News front-end team. TNTWeb is committed to exploring cutting-edge technologies in the industry and improving the personal ability of team members. For front-end developers, the latest high-quality content in the field of small programs and web front-end technology is sorted out and updated every week ✨, Welcome to star, github address: https://github.com/tnfe/TNT-Weekly

Keywords: Javascript Front-end React react-hooks

Added by KaFF on Sun, 13 Feb 2022 14:42:06 +0200