This article is used to take notes on learning and using React Hook.

Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class. A Hook is inside a classDon’tIt works. But you can use them instead of classes.

What can a Hook do?

  • Used in function componentsstate(useState)
  • Extract component state logic (custom hook)

Initialize the

Use create-react-app to initialize projects that support TypeScript syntax.

npx create-react-app base-demo --typescript
Copy the code

useState

Before hooks were introduced, function components (stateless components) were commonly used for UI rendering, represented by data passed through props. If a component wants to have its own state data, it can only do so through the class component. UseState provides methods for preserving its own state in function components. Usage:

import React, {useState} from 'react';
const Counter: React.FC = () = >{
    const [counter, setCounter] = useState(0);
    return <div>
        <span>{counter}</span><button onClick={()= >{setCounter(counter+1)}}>+1</button>
    </div>
}
export default Counter;
Copy the code

Counter is the variable defined, setCounter is the method used to change the value of counter.

useEffect

Effect Hook allows you to perform side effects in function components.

If you’re familiar with React class lifecycle functions, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount.

Effect that does not need to be cleaned

We want to do the same when the component loads and updates. Conceptually, we want it every timeApply colours to a drawingThen it executes — but the React class component doesn’t provide such a method. Even if we extract a method, we still have to call it in two places.

import React, { useState, useEffect } from 'react';
const Counter: React.FC = () = >{
    const [counter, setCounter] = useState(0);
    const [flag, setFlag] = useState(true);
    useEffect(() = >{
        document.title = `You clicked ${counter} times`;
        console.log("useEffect...")});return <div>
        <span>{counter}</span>
        <button onClick={()= >{setCounter(counter+1)}} style={{marginLeft: "20px"}}>+1</button>
        <button onClick={()= >{setFlag(! flag)}} style={{marginLeft: "20px"}}>{flag ? 'ON':'OFF'}</button>
    </div>
}
export default Counter;
Copy the code
  • useEffectThe function is in everyApply colours to a drawingWhat does that mean? In the example, even though we are changing the value of flag, we defined ituseEffectFunctions are also executed. That’s not what we want,useEffectThe second argument in the function can be used to control the timing of execution.

Control useEffect execution

UseEffect actually takes two parameters:

  • If the second parameter is not written by default,useEffect(()=>{});So useEffect function every pageApply colours to a drawingWill be executed.
  • If the second argument is an empty arrayuseEffect(()=>{}, []);The useEffect function is executed independently of any other variables and only on the first page rendering.
  • If the useEffect function is executed in relation to a variable that requires some action when it changes, it can be passed into an array.useEffect(()=>{}, [counter]);
  useEffect(() = >{
      document.title = `You clicked ${counter} times`;
      console.log("useEffect...")
  }, [counter]);
Copy the code

The useEffect function is executed only when the page’s counter data changes.

Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser update screen, making your application seem more responsive.

Effects that need to be cleared

There are also side effects that need to be removed. For example, events bound to document, setTimeout functions, etc. In this case, cleaning is very important to prevent memory leaks! Now let’s compare the implementation using Class and Hook. For example, in the Class component, we implement this:

import React from 'react';
class MouseTrigger extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            position: {
                x: 0.y: 0
            }
        }
    }
    handleMouseMove = (e) = >{
        this.setState({
            position: {
                x: e.clientX,
                y: e.clientY
            }
        })
    }
    // Add listening events
    componentDidMount(){
        document.addEventListener("click".this.handleMouseMove);
    }
    // Cancel the listening event
    componentWillUnmount(){
        document.removeEventListener("click".this.handleMouseMove);
    }
    render(){
        return (
            <div>
                <p>Mouse position is ({this.state.position.x}, {this.state.position.y})</p>
            </div>)}}export default MouseTrigger;
Copy the code

Implemented using the useEffect method, useEffect returns a function that clears “side effects”.

import React, { useState, useEffect} from "react";
const MouseTrigger: React.FC = () = >{
    const [position, setPosition] = useState({x: 0.y:0})
    const handleMouseMove = (e: MouseEvent) = >{
        setPosition({
            x: e.clientX,
            y: e.clientY
        })
    }
    useEffect(() = >{
        document.addEventListener("click", handleMouseMove);
        return () = >{
            document.removeEventListener("click", handleMouseMove); }}, [])return <p>
        Mouse position is ({position.x}, {position.y})
    </p>
}
export default MouseTrigger;
Copy the code

useEffect(()=>{… Return ()=>{}}, []), the second argument is an empty array [], so that useEffect is executed only on the first page load; Returns a function that is executed when the component is unloaded to remove added “side effects”.

No contrast, no harm. Obviously, the function component defined by useEffect is more clear and concise, giving a refreshing feeling.

Customize the Hook

With custom hooks, component logic can be extracted into reusable functions. Before custom hooks were proposed, how did we implement them? There are two common ways, HOC and render props. Let’s take a look at the differences in usage

HOC

HOC is short for High Order Component, meaning “high-level Component.” A higher-order component is essentially a function that takes a component as an argument and returns a new component. In business, there is often a need to request data. Loadin animation is displayed during data request and corresponding data is displayed upon completion of the request. This logic can be stripped down as follows:

// FetchData.js
import React from 'react';
import axios from 'axios';
const FetchData = (Component, url) = >{
    class WithFetchData extends React.Component{
        constructor(props){
            super(props);
            this.state = {
                data: [].isLoading: true}}componentDidMount(){
            this.setState({
                isLoading: true
            });
            axios.get(url).then(res= >{
                if(res.status===200) {this.setState({
                        data: res.data,
                        isLoading: false})}})}render(){
            const {isLoading, data} = this.state;
            return <>
                { isLoading ? <p>data is loading</p>: <Component data={data}/>}
            </>}}return WithFetchData;
}
export default FetchData;
Copy the code

FetchData is a higher-order component, essentially a function that takes a component and returns a new component. Use in required components:

// ShowData.js
import React from 'react';
import FetchData from './FetchData';
const ShowData = (props) = >{
    return <>
        {props.data.map((item, key)=><p key={key}>{item.name}, {item.price}</p>)}
    </>
}
export default FetchData(ShowData, "/api/mockData.json");
Copy the code

HOC is essentially extracting the component logic into the parent component and wrapping it around it so that internal components can reuse externally defined data and logic.

render props

Render props is a technique that utilizes parent-child component pass-throughs. The component to be displayed is passed to the parent component via the Render function.

// RenderPropsDemo.js
import React from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
class RenderPropsDemo extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            data: [].isLoading: true}}componentDidMount(){
        this.setState({
            isLoading: true
        });
        const {url} = this.props;
        axios.get(url).then(res= >{
            if(res.status===200) {this.setState({
                    data: res.data,
                    isLoading: false})}})}render(){
        const {isLoading, data} = this.state;
        const {render} = this.props;
        return (
            <>
                { isLoading ? <p>data is loading</p> : <>{render(data)}</>}
            </>
        )
    }
}
RenderPropsDemo.prototypes = {
    render: PropTypes.func.isRequired
}
export default RenderPropsDemo;
Copy the code

Use:

// ShowData.js
import React from 'react';
import RenderPropsDemo from './RenderPropsDemo';

const ShowData = () = >{
    const render = (data) = >{
        return data.map((item, key) = ><p key={key}>{item.name}, {item.price}</p>)}return <>
        <RenderPropsDemo render={render} url="/api/mockData.json"/>
    </>
}
export default ShowData;
Copy the code

Customize the Hook

With custom hooks, component logic can be extracted into reusable functions. In contrast to HOC and render props, custom hooks are pure function modes that don’t involve parent and child components. The logic is clearer.

//useFetchData.tsx
import { useState, useEffect } from 'react';
import axios from 'axios';
// Only the first request is initiated by default
const useFetchData = (url: string, deps: any[] = []) = >{
    const [data, setData] = useState<any>([]);
    const [loading, setLoading] = useState(false);
    useEffect(() = >{
        setLoading(true);
        axios.get(url).then(res= >{
            if(res.status===200){
                setData(res.data);
                setLoading(false);
            }
        })
    }, deps);
    / / return
    return [data, loading];
}

export default useFetchData;
Copy the code

Use:

//FetchData.tsx
import React from 'react';
import useFetchData from './useFetchData';
interface IShowDataResult{
    name: string;
    price: number;
}
type resultType = Array<IShowDataResult>;
const FetchData: React.FC = () = >{
    // Run useFetchData to obtain the file
    const [data, loading] = useFetchData("/api/mockData.json");
    return <>
        {
            loading ? <p>data is loading</p> : 
            <>
                {data.map((item:IShowDataResult, key: number)=><p key={key}>{item.name}</p>)}
            </>} < / a >}export default FetchData;
Copy the code

The effect of the custom Hook implementation is similar to the Composition API idea in Vue 3.0. The idea is to strip the repetitive logic out of components and manage the code in a cleaner, more organized way.

Note:

  • Custom hooks must start with “use”. This agreement is very important. Otherwise, React will not automatically check if your hooks violate Hook rules, since it is impossible to determine whether a function contains calls to its internal hooks.
  • Two components using the same Hook do not share state. A custom Hook is a mechanism for reusing state logic (for example, set to subscribe and store current values), so every time a custom Hook is used, all state and side effects are completely isolated.

useRef

Gets the latest value

Take a look at the following example to compare the use of useState and useRef:

import {useState, useRef, useEffect} from 'react';
const Counter = () = >{
    const [counter, setCounter] = useState(0);
    const likeRef = useRef(0);
    const handleAlert = () = >{
        setTimeout(() = >{
            alert(counter);
            alert(likeRef.current);
        }, 2000);
    }
    return (
        <div>
            <span>{counter}</span>
            <button onClick={()= >{setCounter(counter+1); likeRef.current++; }} style={{marginLeft: '20px'}}>+1</button>
            <button onClick={handleAlert} style={{marginLeft: '20px'}} >alert</button>
        </div>)}export default Counter;
Copy the code

When we click the Alert button, we can see that the value of counter is not up to date, but likeref.current is.

Why is that?

This is because when we change the state React will re-render the component each time, each rendering will get a separate counter value and re-render a handleAlert function. Each handleAlert closure holds the render counter. That is, the last rendering has nothing to do with the next one. The next rendering does not affect the previous data. This is characteristic of state and props.

UseRef returns a mutable ref object. Ref keeps a unique reference in all render, so ref values get the final state instead of being isolated.

To obtain

UseRef can be used to retrieve and manipulate the DOM. An example: The input field is automatically focused when the page loads.

import {useState, useRef, useEffect} from 'react';
const Counter = () = >{
    const inputRef = useRef(null);
    useEffect(() = >{
        if(inputRef && inputRef.current){ inputRef.current.focus(); }}, [])return (
        <div>
            <input type="text" ref={inputRef}/>
        </div>)}export default Counter;
Copy the code

useContext

The state defined globally is available in the child component. When these states are changed, the child components used are triggered to re-render.

  • React.createContext(themes.light);Initialize the global state, default themes.light
  • < themecontext. Provider value={themes.dark}> Assign themes.dark
  • useContextChild components use global state.

// initializes the global state in app.js
import './App.css';
import React, {useState} from 'react';
import Counter from "./Counter";

const themes = {
  light: {
    foreground: "# 000000".background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff".background: "# 222222"}}const ThemeContext = React.createContext(themes.light);
// Needs to be exported for use by child components.
export {ThemeContext};

function App() {
  const [curTheme, setCurTheme] = useState(true);
  const changeTheme = () = >{ setCurTheme(! curTheme); }return (
    <ThemeContext.Provider value={curTheme ? themes.light: themes.dark} >
      <Counter/>
      <button onClick={changeTheme}>change</button>
    </ThemeContext.Provider>
  );
}
export default App;
Copy the code

Use in child components:

import {useState, useRef, useEffect, useContext} from 'react';
import { ThemeContext } from './App';
const Counter = () = >{
     // Some code is omitted
    const theme = useContext(ThemeContext);
    const themeStyle = {
        background: theme.background, 
        color: theme.foreground
    }
    return (
        <div>
            <input type="text" style={themeStyle}/>
        </div>)}export default Counter;
Copy the code

UseMemo and useCallback

background

For a series of optimization stories triggered by a problem, see the following example:

class Foo extends React.Component{
    render(){
        console.log("render...")
        return (
            <p>{this.props.count}</p>)}}class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            count: 0.double: 1
        }
    }
    changeDouble = () = >{
        this.setState({
            double: this.state.double * 2})}render(){
        return (
            <>
                <Foo count={this.state.count}/>
                <p>count: {this.state.count}, double: {this.state.double}</p>
                <button onClick={this.changeDouble}>Double</button>
            </>)}}Copy the code

Normally we want the child Foo to be rerendered only if the parent count value changes, but in practice every time the parent renders itself, the child is also rendered. When we modify the value of double, the child component is also rerender. How do you solve this problem? There are the following methods:

  • Solution a:shouldComponentUpdate(nextProps, nextState)Redefining whether an update is required in the lifecycle function
  • Plan 2: UsePureComponentComponent to solve

ShouldComponentUpdate (nextProps, nextState) Life cycle function returns a Boolean, true by default, meaning that the component is rerendered each time. In this function, we can compare the value of the nextProps to be modified against the value of the current props. If it is the same, the component will not be re-rendered. Otherwise, return true and the component needs to be re-rendered.

class Foo extends React.Component{
    shouldComponentUpdate(nextProps, nextState){
        if(nextProps.count === this.props.count){
            return false;
        }
        return true; }}Copy the code

Solution 2: Use PureComponent.

class Foo extends React.PureComponent{
    render(){
        console.log("render...")
        return (
            <p>{this.props.count}</p>)}}Copy the code

Inside the PureComponent is a superficial comparison of nextProps and this.props in the shouldComponentUpdate lifecycle function. Note that since this is a shallow comparison, there is a problem if we pass a callback function to a child component. Look at the following example:

class App extends React.Component{
    render(){
        return (
            <>
                <Foo count={this.state.count} cb={()= >{}} / ><button onClick={this.changeDouble}>Double</button>
            </>)}}Copy the code

Every time the parent APP modifies the value of double, it causes its own render function to be executed. Cb ={()=>{}} is reexecuted when render is executed, so it is a different callback object each time. This results in repeated rendering of child components.

Solution: We need to make the callback function a variable in a class to avoid this problem.

class App extends React.Component{
    cb = () = >{}
    render(){
        return (
            <>
                <Foo count={this.state.count} cb={this.cb}/>
            </>)}}Copy the code

At this point, you can avoid the problem of useless repeated rendering of child components in the class component. But in functional components, these problems still exist.

We usually express components that have no state of their own as function components. Function components do not have shouldComponentUpdate and PureComponent. The corresponding solution is memo.

import React, {memo} from 'react';
const Foo = memo((props) = > {
    console.log("render...")
    return (
        <p>{props.count}</p>)})Copy the code

Is that the end of it? Don’t. When using React Hook, our function component has the state feature, so our parent App is no longer just a class component. It can also be a function component. As follows:

const Foo = memo((props) = > {
    console.log("render...")
    return (
        <p>{props.count}</p>)})const App = () = >{
    const [count, setCount] = useState(0);
    const [double, setDouble] = useState(1);
    const cb = () = >{}
    return (
        <>
            <Foo count={count} cb={cb}/>
            <p>count: {count}, double: {double}</p>
            <button onClick={()= >{setDouble(double*2)}}>Double</button>
        </>)}Copy the code

You can see that even if we store the callback function with a variable, the child component is rendered repeatedly each time. This is because the App itself is a functional component that executes independently of each other. Cb is not retained every time. This is where useMemo comes in.

useMemo

Returns a new data

The syntax of useMemo is the same as useEffect. Unlike useEffect, however, useMemo is executed before rendering.

const App = () = >{
    const [count, setCount] = useState(0);
    const [double, setDouble] = useState(1);
    const sum = useMemo(() = >{
        return count + double;
    }, [count])

    return (
        <>
            <p>Count: {count}, double: {double}, sum: {sum}</p>
            <button onClick={()= >{setDouble(double*2)}}>Double</button>
            <button onClick={()= >{setCount(count+1)}}>count++</button>
        </>)}Copy the code

When count and double change arbitrarily, sum changes as well. It has a bit of a computed feel in Vue.

Return a function

Going back to the previous question, if a parent component passes a function to a child component, how can we ensure that the child component does not repeat the rendering? UseMemo (()=>{return ()=>{}}, []) passes the empty array [] as the second argument, indicating that it will only be executed on the first time. This ensures that the function is the same and that the subcomponents will not be re-rendered.

const App = () = >{
    const [count, setCount] = useState(0);
    const [double, setDouble] = useState(1);
    const cb = useMemo(() = >{
        return () = >{
           console.log("cb...")}}, [])return (
        <>
            <Foo count={count} cb={cb}/>
        </>)}Copy the code

So, how does useCallback work? That is, when useMemo returns a function, we can use the shorthand useCallback. The following

const App = () = >{
    const [count, setCount] = useState(0);
    const [double, setDouble] = useState(1);
    const cb = useCallback(() = >{
        console.log("cb..")}, [])return (
        <>
            <Foo count={count} cb={cb}/>
        </>)}Copy the code

The last

If there is any mistake or not precise place, please give correction, thank you very much. If you like or have some inspiration, welcome to like, to the author is also a kind of encouragement.