preface

When I first came into contact with the front-end MV* framework, React was the first thing I came into contact with. At that time, IT was still in the so-called “Dachang”, but it was still early at that time, so I came into contact with version 14. Later, after leaving here, I entered a start-up company.

Recently, I plan to learn React again. I have read a lot of articles about Hooks on the Internet. After systematic study and summary, I have created this article

What is the Hooks

  • React-hooks are the react-hooks API that allows you to use state and other features of React without writing classes
  • Hooks are functions that allow you to “Hook” React State and lifecycle features into function components

Why use Hooks

Disadvantages of class components

  • Reusing state logic between components is difficult
  • Complex components become difficult to understand
  • Hard to understand class

You have to understand how this works in JS, which is quite different from other languages, and don’t forget to bind event handlers. There is no stable syntax proposal, and the code is very redundant

Hooks solve the above problem. In addition, there are some other things to do:

  • Increase code reusability and logic to make up for the defects of stateless components with no life cycle and no data management state
  • The react-hooks idea is closer to functional programming. Es6. Which is the result of which are Hooks that need to write the render function in the class declaration cycle.

Hooks do not contain damaging changes

  • Completely optional: You can try a Hook in some components without rewriting any existing code, but you don’t have to learn or use it now if you don’t want to
  • 100% backward compatibility: Hooks do not eat any destructive changes
  • Now available: Hooks are released in V16.8

Rule for using Hooks

  1. Use hooks only at the top level; do not call hooks in loops, conditions, or nested functions

Make sure to always call them at the top of your React function. By following this rule, you can ensure that hooks are called in the same order every time you render. This allows React to keep the hook state correct between multiple useState and useEffect calls

  1. Only the React function calls the Hook

Instead of calling a Hook in a normal JS function, you can

  • Call a Hook in the React function component
  • Call other hooks in a custom Hook

useState

const [state , setState] = useState(initialState)

  • UseState takes one parameter, initialState (which can be a function that returns a value), which can be of any data type and is generally used as the default
  • UseState returns an array whose first argument is the state we need to use and whose second argument is a function that changes the state (same as this.setstate).

useEffect

Effect Hook allows you to perform side effects (fetch data, check Settings, and manually change the DOM in the React component) in function components

useEffect(fn , array)

UseEffect is executed once after the first rendering, with a second parameter to simulate some life cycle of the class

UseEffect Hook can be regarded as the combination of three functions componentDidMount componentDidUpdate and componentWillUnmount.

UseEffect implementation componentDidMount

UseEffect equals componentDidMount if the second argument is an empty array

import React , { useState , useEffect } from 'react'
function Example(){
    const [count , setCount] = useState(0);
    useEffect(() = >{
        console.log("I'm only going to do it after the component is first mounted."); } []);return (
        <div>
            <p>xxxxx {count} </p>
            <button onClick={()= > setCount(count +1)}}>Click me</button>
        </div>)}export default Example;
Copy the code

After the page is rendered,useEffect will be executed once and log will be printed. When the button is clicked to change the state and the page is rendered again,useEffect will not be executed.

UseEffect implementation componentDidUpdate

If the second parameter is not passed,useEffect is executed on the first render and every update

import React, { useState, useEffect } from "react";
function Example() {
  const [count, setCount] = useState(0);
  useEffect(() = > {
    console.log("I'll do it after the first component is mounted and when I re-render.");
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()= > setCount(count + 1)}>Click me</button>
    </div>
  );
}
export default Example;

Copy the code

On the first render, a useEffect is executed, printing out “I will do this after the first component is mounted and when I re-render”. When the button is clicked, the state is changed, the page is re-rendered, useEffect is executed, printing “I will execute after the first component is mounted and when the component is re-rendered”.

UseEffect implementation componentWillUnmount

Effect returns a function that React calls when a clean operation is performed

useEffect(() = > {
    console.log("Subscribe to some events");
    return () = > {
      console.log("Perform cleanup operation")}}, []);Copy the code

Note: ‘Perform cleanup’ is printed not only for component destruction, but also for every re-render. The reason is clearly explained on the website. Check it out

Control the useEffect execution

import React, { useState, useEffect } from "react";
function Example() {
  const [count, setCount] = useState(0);
  const [number, setNumber] = useState(1);
  useEffect(() = > {
    console.log("I'm only going to execute when Cout changes.");
  }, [count]);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()= > setCount(count + 1)}>Click cout</button>
      <button onClick={()= > setNumber(number + 1)}>Click number</button>
    </div>
  );
}
export default Example;

Copy the code

If there is an element in the array, the element’s value has been changed and then rerendered.

Use multiple Useeffects for separation of concerns

One of the goals of using hooks is to solve the problem that class lifecycle functions often contain unrelated logic, but separate the related logic into several different methods.

import React, { useState, useEffect } from "react";
function Example() {
  useEffect(() = > {
    A / / logic
  });
  useEffect(() = > {
    2 / / logic
  });
   useEffect(() = > {
    3 / / logic
  });
  return (
    <div>The use of useEffect</div>
  );
}
export default Example;

Copy the code

Hooks allow us to separate code according to its purpose, rather than life cycle functions. React will call each effect in the component in the order in which it is declared

UseEffect uses asynchronous functions

UseEffect cannot use async await directly

Incorrect usage:

/* effect does not support direct async await*/
 useEffect(async() = > {/* Request data */
      const res = await getData()
 },[])

Copy the code

The useEffect callback argument returns a clean up function that clears side effects. Therefore, there is no way to return a Promise and still less async/await

Usage Method 1 (recommended)

const App = () = > {
  useEffect(() = >{(async function getDatas() {
      await getData();
    })();
  }, []);
  return <div></div>;
};

Copy the code

Usage Method 2

  useEffect(() = > {
    const getDatas = async() = > {const data = awaitgetData(); setData(data); }; getDatas(); } []);Copy the code

What does the useEffect do

By using the useEffect Hook, you can tell the React component that it needs to do something after rendering. React will save the function you passed and call it after performing a DOM update.

Why is useEffect called inside a component

Putting useEffect inside the component allows us to access the state variable (or other props) directly in effect. We don’t need a special API to read it – it’s already stored in function scope. Hook uses the MECHANISM of JS closures rather than introducing a specific React API when JS already provides a solution

useContext

concept

const value = useContext(MyContext) ;

Receives a context object (the return value of React. CreateContext) and returns the current value of the context. When the most recent

update is made to the component’s upper layer, the Hook triggers a re-rendering and uses the latest context value passed to MyContext Provider. Even if the ancestor used React. Memo or shouldComponentUpdate will be rerendered when the component itself uses useContext.

Remember that the argument to useContext must be the context object itself:

  • Right: useContext (MyContext)
  • Error: useContext (MyContext. Consumer)
  • Error: useContext (MyContext. The Provider)

The sample

index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// Create two contexts
export const UserContext = React.createContext();
export const TokenContext = React.createContext();
ReactDOM.render(
  <UserContext.Provider value={{ id: 1.name: "chimmy", age: "20}} ">
    <TokenContext.Provider value="I am a token">
      <App />
    </TokenContext.Provider>
  </UserContext.Provider>.document.getElementById("root"));Copy the code

app.js

import React, { useContext } from "react";
import { UserContext, TokenContext } from "./index";

function Example() {
  let user = useContext(UserContext);
  let token = useContext(TokenContext);
  console.log("UserContext", user);
  console.log("TokenContext", token);
  return (
    <div>name:{user? .name},age:{user?.age}</div>
  );
}
export default Example;

Copy the code

prompt

If you’re already familiar with the Context API before you hit the Hook, it should make sense, UseContext (MyContext) is equivalent to static contextType = MyContext or < myContext.consumer > in the class component.

UseContext (MyContext) just lets you read the value of the context and subscribe to changes in the context. You still need to use < myContext.provider > in the upper component tree to provide the context for the lower component

useReducer

concept

const [state , dispatch] = useReducer(reducer , initialArg , init) ;

An alternative to useState. It receives a Reducer of the form (state,action) => newState and returns the current state and its accompanying dispatch method.

UseReducer can be more useful than useState in some situations, such as when the state logic is complex and contains multiple subvalues, or when the next state depends on the previous state. Also, using useReducer can optimize performance for components that trigger deep updates because you can pass dispatches to child components instead of callbacks.

Note:

React ensures that the identity of the Dispatch function is stable and does not change when a component is rerendered, which is why it is safe to omit Dispatch from useEffect or useCallback’s dependency list

Example:

import React, { useReducer } from "react";
export default function Home() {
  function reducer(state, action) {
    switch (action.type) {
      case "increment":
        return { ...state, counter: state.counter + 1 };
      case "decrement":
        return { ...state, counter: state.counter - 1 };
      default:
        returnstate; }}const [state, dispatch] = useReducer(reducer, { counter: 0 });
  return (
    <div>
      <h2>Home Current count: {state.counter}</h2>
      <button onClick={(e)= > dispatch({ type: "increment" })}>+1</button>
      <button onClick={(e)= > dispatch({ type: "decrement" })}>-1</button>
    </div>
  );
}

Copy the code

useCallback

Concept:

const memoizedCallback = useCallback(
  () = > {
    doSomething(a, b);
  },
  [a, b],
);

Copy the code

Returns a memoized callback function

Passing the inline callback function and the array of dependencies as arguments to useCallback returns the Memoized version of the callback function, which is updated only when a dependency changes. This is useful when you pass callback data to child components that are optimized and use reference equality to avoid unnecessary rendering

Example:

import React, { useState } from "react";
/ / child component
function Childs(props) {
  console.log("The child component is rendered.");
  return (
    <>
      <button onClick={props.onClick}>Change the title</button>
      <h1>{props.name}</h1>
    </>
  );
}
const Child = React.memo(Childs);
function App() {
  const [title, setTitle] = useState("This is a title.");
  const [subtitle, setSubtitle] = useState("I am a subtitle");
  const callback = () = > {
    setTitle("The title has changed.");
  };
  return (
    <div className="App">
      <h1>{title}</h1>
      <h2>{subtitle}</h2>
      <button onClick={()= >SetSubtitle (" subtitle changed ")}> Change the subtitle</button>
      <Child onClick={callback} name="Peach peach" />
    </div>
  );
}

Copy the code

When the ‘change subtitle’ button is clicked, the subtitle changes to ‘subtitle changed’ and the console prints’ child rendered ‘. This proves that the child has been rerendered, but the child has not changed at all. This re-rendering of the Child component is superfluous.

Looking for a reason

Before solving the problem, first of all, what is the cause of the problem

There are three ways to re-render a component:

  • Either the state of the component itself changes
  • Either the parent component rerenders, causing the child component to rerender, but the props of the parent component does not change
  • Either the parent component rerenders, causing the child component to rerender, but the props passed by the parent component changes

Check from the above reasons:

The first one is obvious, and does not change the state of the child component when the button is clicked

We use react. memo to solve this problem

When the parent component rerenders, the porps passed to the Child component have changed, and the two properties passed to the Child component, name and onClick, will not change. Why does the callback passed to onClick change? Each time the function component is re-rendered, the callback function must have changed, causing the child component to be re-rendered.

Solve the problem with useCallback

const callback = () = > {
  doSomething(a, b);
}
const memoizedCallback = useCallback(callback, [a, b])

Copy the code

Passing the function and its dependencies as arguments to useCallback will return the Memoizedversion of the callback function, which will be updated only if the dependency changes.

Make sure to modify the callback function passed to the Child component as follows:

const callback = () = > { setTitle("The title has changed."); };
// Use useCallback to do the memory callback and pass the memory callback to Child
<Child onClick={useCallback(callback, [])} name="Peach peach" />

Copy the code

This way we can see that rendering only happens on the first render. It will no longer print when changing subheadings and titles

useMemo

concept

const cacheSomething = useMemo(create , deps)

  • The first argument to create is a function whose return value is cached
  • The second parameter of dePS is an array that stores the current useMemo dependencies. When the function component is executed next time, it will compare the state of the DEPS dependencies to see if there is any change. If there is any change, useMemo will be executed again to obtain the new cache value.
  • CacheSomething return value. The return value of create is executed. If a dePS dependency changes, the return value of create is executed again

UseMemo principle

UseMemo records the return value of the last create execution and binds it to the fiber object corresponding to the function component. The cached value will remain as long as the component is not destroyed, but if there is a change in the DEPS, create will be re-executed and the return value will be recorded to Fiber as the new value On the object.

Example:

function Child(){
    console.log("The child component is rendered.")
    return <div>Child</div> 
}
const Child = memo(Child)
function APP(){
    const [count, setCount] = useState(0);
    const userInfo = {
      age: count,
      name: 'jimmy'
    }
    return <Child userInfo={userInfo}>
}

Copy the code

When the function component is rerendered,userInfo is a new object each time, causing the Child component to be rerendered regardless of whether count has changed

The following one does not return a new object until the count changes

function Child(){
    console.log("The child component is rendered.")
    return <div>Child</div> 
}
function APP(){
    const [count, setCount] = useState(0);
    const userInfo = useMemo(() = > {
      return {
        name: "jimmy".age: count
      };
    }, [count]);
    return <Child userInfo={userInfo}>
}
Copy the code

In fact, useMemo does more than that. According to the official documentation, you can put some expensive computing logic into useMemo and update it only when the dependency value changes.

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

// The function that computes and is expensive
function calcNumber(count) {
  console.log("CalcNumber recalculation");
  let total = 0;
  for (let i = 1; i <= count; i++) {
    total += i;
  }
  return total;
}
export default function MemoHookDemo01() {
  const [count, setCount] = useState(100000);
  const [show, setShow] = useState(true);
  const total = useMemo(() = > {
    return calcNumber(count);
  }, [count]);
  return (
    <div>
      <h2>Calculate the sum of numbers: {total}</h2>
      <button onClick={e= > setCount(count + 1)}>+1</button>
      <button onClick={e= >setShow(! The show)} > show switch</button>
    </div>)}Copy the code

When the show toggle button is clicked, the calcNumber function that calculates the sum does not render, only when count changes

Summary of useCallback and useMemo

UseCallback and useMemo cache a function and cache the return of a function You can optimize the current component as well as its subcomponents, optimizing the current component mainly through memoize to cache some of the more complex computational logic, although there is no need to use useMemo for simple calculations

We can implement useCallback flexibly by defining the return value of useMemo as returning a function

UseCallback (fn, deps) equivalent to useMemo(()=> fn, deps)

useRef

const refContainer = useRef(initialValue);

UseRef returns a mutable ref object whose.current property is initialized as the passed parameter, and the returned REF object remains unchanged for the lifetime of the component

UseRef for dom

UseRef has a parameter that can be used as the initial value of cached data, the return value can be marked by the DOM element ref, and the marked element node can be retrieved.

import React, { useRef } from "react";
function Example() {
  const divRef = useRef();
  function changeDOM() {
    // Get the entire div
    console.log("The whole div", divRef.current);
    // Get the div class
    console.log("Div class." ", divRef.current.className);
    // Get the div custom attribute
    console.log("Div custom attribute", divRef.current.getAttribute("data-clj"));
  }
  return (
    <div>
      <div className="div-class" data-clj="I'm a custom attribute for div." ref={divRef}>I am a div</div>
      <button onClick={(e)= >The DOM changeDOM ()} ></button>
    </div>
  );
}
export default Example;

Copy the code

UseRef caches data

Another important function of useRef is to cache data. We know that the useState useReducer can save the current data source. But useRef is a great choice if they update the data source’s function execution rewrite that results in the entire component being reexecuted to render, and if you declare variables inside the function component, the next update will reset, if you want to save data quietly without triggering function updates

import React, { useRef, useState, useEffect } from "react";
function Example() {
  const [count, setCount] = useState(0);

  const numRef = useRef(count);

  useEffect(() = > {
    numRef.current = count;
  }, [count]);

  return (
    <div>
      <h2>Count last value: {numref.current}</h2>
      <h2>Count: {count}</h2>
      <button onClick={(e)= > setCount(count + 10)}>+10</button>
    </div>
  );
}
export default Example;

Copy the code

When the ref object’s contents change, UseRef does not tell you that the.current property does not cause the component to re-render, so in the box above, numref.current has changed, but the page still shows the last value, and when it is updated, it will show the last value.