Reference article:

Juejin. Cn/post / 684490…

Juejin. Cn/post / 684490…

Juejin. Cn/post / 684490…

1. Which are fixed by Hooks

(1) The deficiency of class components

  • State logic is hard to reuse:

It is difficult to reuse state logic between components, perhaps using render props or HOC. However, both render properties and high-level components will wrap a layer of parent containers (usually div elements) around the original component, resulting in hierarchical redundancy that becomes complex and difficult to maintain:

  • To mix irrelevant logic in a lifecycle function:

Mixing irrelevant logic in life cycle functions (registering events and other logic in componentDidMount, unmounting events in componentWillUnmount)

Class components have access to and processing of state all over the place, making components difficult to break down into smaller components

  • This points to the problem:

When a parent passes a function to a child, it must bind this

(2) the advantages of hooks

  • Can optimize three major problems for class components
  • Reusing state logic without modifying component structure (custom Hooks)
  • Ability to break down interrelated parts of a component into smaller functions (such as setting up subscriptions or requesting data)
  • Separation of concerns for side effects: Side effects are the logic that does not occur during the data-to-view transformation, such as Ajax requests, accessing native DOM elements, local persistent caching, binding/unbinding events, adding subscriptions, setting timers, logging, etc. In the past, these side effects were written in the class component lifecycle functions. UseEffect is executed after the rendering is complete, useLayoutEffect is executed after the browser layout and before the painting.

2. Use premise

  • Call a Hook only from the outermost layer inside a function, never from a loop, conditional judgment, or child function
  • You can only call a Hook in the React function component, not in other JavaScript functions

3. useState

import { useState } from 'react';

function Example() {
  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

UseState returns an array: a state, and a function to update the state

The only argument to useState is the initialState. During initial rendering, the state returned is the same as the value of the first argument passed in.

Matters needing attention:

  • React assumes that when you call useState multiple times, you can guarantee that the order of their calls will remain the same each time you render. React determines which State you update when you update the State value based on the order in which each useState is defined
  • You can call this function in an event handler or some other place. It is similar to the this.setState of the class component, but it does not merge the new state with the old state, instead replacing it directly
  • The initialState parameter is only used in the initial rendering of the component and will be ignored in subsequent renderings
  • The Hook uses object. is to compare the old state with the new state. Unlike the setState method in the class component, if you change the state and the passed state value does not change, the Hook does not re-render
  • UseState cannot store functions or function components directly; it calls the function and stores or updates the return value of the function as the final state value. If you must, you can store it as an element of an array or as an attribute of an object
  • UseState does not set a callback like setState in the class component to get the latest state and then proceed; This can be done by useEffect and setting the corresponding dependencies. UseEffect is called after rendering is complete

The state of useState is not synchronized in an asynchronous operation:

Functions operate independently, and each function has its own scope. Function variables are stored in the runtime scope. When we have asynchronous operations, we often run into asynchronous callbacks whose variable references are old (also known as closures). Here’s an example:

import React, { useState } from "react";
​
const Counter = () = > {
  const [counter, setCounter] = useState(0);
​
  const onAlertButtonClick = () = > {
    setTimeout(() = > {
      alert("Value: " + counter);
    }, 3000);
  };
​
  return (
    <div>
      <p>You clicked {counter} times.</p>
      <button onClick={()= > setCounter(counter + 1)}>Click me</button>
      <button onClick={onAlertButtonClick}>
        Show me the value in 3 seconds
      </button>
    </div>
  );
};
​
export default Counter;
Copy the code

After clicking Show me the value in 3 seconds, Click Click me to change the value of counter from 0 to 1. After three seconds, the timer fires, but alert returns 0 (the old value), but we want the result to be the current state 1.

This problem does not occur in class Component because class Component properties and methods are stored on an instance called this.state.xxx and this.method(). Because the value is evaluated from a constant instance each time, there is no old reference problem.

In addition to asynchrony like setTimout, access State in similar event listeners (such as scrolllistening callbacks) will also be old

The current common solution to this problem is to use useRef (see below)

4. useEffect

What are side effects in React?

Refers to logic that does not occur during the data-to-view transformation, such as Ajax requests, accessing native DOM elements, local persistent caching, binding/unbinding events, adding subscriptions, setting timers, logging, etc.

Side effects can be divided into two categories: those that need to be cleaned up (such as event binding/unbinding) and those that do not.

Changing the DOM, sending Ajax requests, and performing other side effects inside function components (in this case, during the React rendering phase) were previously not allowed, as they could cause unexplained bugs and break UI consistency

One requirement implementation: you need document.title to show your latest coutnt in real time.

Class component implementation:

class Counter extends React.Component{
    state = {number:0};
    add = () = >{
        this.setState({number:this.state.number+1});
    };
    componentDidMount(){
        this.changeTitle();
    }
    componentDidUpdate(){
        this.changeTitle();
    }
    changeTitle = () = >{
        document.title = 'You've already clickedThe ${this.state.number}Time `;
    };
    render(){
        return (
            <>
              <p>{this.state.number}</p>
              <button onClick={this.add}>+</button>
            </>)}}Copy the code

Because you want document.title to show your latest hit count (Coutnt) in real time, you have to write repetitive code in componentDidMount or componentDidUpdate to reset Document. title

Hooks implementation:

import React,{Component,useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
function Counter(){
    const [number,setNumber] = useState(0);
    UseEffect is executed after the first rendering and after the update
    ComponentDidMount and componentDidUpdate:
    useEffect(() = > {
        document.title = 'You clicked${number}Time `;
    });
    return (
        <>
            <p>{number}</p>
            <button onClick={()= >setNumber(number+1)}>+</button>
        </>
    )
}
ReactDOM.render(<Counter />.document.getElementById('root'));

Copy the code

UseEffect is an Effect Hook that gives function components the ability to manipulate side effects. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in the Class component, but has been consolidated into an API

Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser update screen, making your application seem more responsive. In most cases, effects do not need to be executed synchronously. In individual cases (such as measuring layout), there is a separate useLayoutEffect Hook for you to use, with the same API as useEffect.

4.1 Side effects of removal

function Counter(){
    let [number,setNumber] = useState(0);
    let [text,setText] = useState(' ');
    // equivalent to componentDidMount and componentDidUpdate
    useEffect(() = >{
        console.log('Start a new timer')
        let $timer = setInterval(() = >{
            setNumber(number= >number+1);
        },1000);
        UseEffect If a function is returned, it will be called when the component is unloaded and updated
        UseEffect calls the last returned function before executing the side effect function
        // If you want to clear side effects, either return a function that clears side effects
       return () = >{
            console.log('destroy effect');
            clearInterval($timer); }});/ /} []); // Either pass an empty dependency array here, so that it doesn't repeat execution
    return (
        <>
          <input value={text} onChange={(event)= >setText(event.target.value)}/>
          <p>{number}</p>
          <button>+</button>
        </>)}Copy the code

UseEffect receives a function that is executed after the component has been rendered to the screen, with the requirement that it either returns a function that clears side effects or nothing at all

By default, useEffect is executed after the first rendering and after every update. The function that clears the side effects returned by the useEffect received function argument is executed before the component is updated and unloaded, and then after the update, the useEffect received function is executed. Then wait for the next component update or uninstall and perform the function to clear the side effects…… And so on

4.2 Skip effect for performance optimization

By default, useEffect is executed after each update

Sometimes we just want to useEffect only when the component is mounted and then clear the side effects function when the component is unmounted, UseEffect is performed when you do not want to update (such as binding/unbinding of native events) or only when you only want to update specified states (such as updating some states with the latest state for subsequent operations)

At this point, you can tell React to skip the effect call by passing the array as the second optional argument to useEffect:

  • If you want to execute effect once (only when the component is mounted and unmounted), you can pass an empty array ([]) as the second argument. This tells React that your effect doesn’t depend on any value in props or state, so it never needs to be repeated
  • If useEffect is executed only when the specified state is updated, an array containing the specified state element can be passed as the second argument
function Counter(){
    let [number,setNumber] = useState(0);
    let [text,setText] = useState(' ');
    // equivalent to componentDidMount and componentDidUpdate
    useEffect(() = >{
        console.log('useEffect');
        let $timer = setInterval(() = >{
            setNumber(number= >number+1);
        },1000);
    },[text]);// The array represents the efffect dependent variable. Efffect is re-executed only when the variable is changed
    return (
        <>
          <input value={text} onChange={(event)= >setText(event.target.value)}/>
          <p>{number}</p>
          <button>+</button>
        </>)}Copy the code

Note: useEffect is always executed once when the component is mounted, whether or not you specify the second argument to useEffct

4.3 Use multiple UseEffects

UseEffect can be declared multiple times, and React will call each effect in the component in the order in which it was declared

We can classify different kinds of operations into multiple USEeffects according to the nature of specific side effects operations

/ / Hooks
function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
	// Effect related to DOM manipulation
  useEffect(() = > {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
	// Subscribe/unsubscribe related effect
  useEffect(() = > {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () = > {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

Copy the code

4.4 useEffect Cannot accept async as a callback function

Making asynchronous requests is a common scenario in useEffect. Since asynchronous requests are usually wrapped asynchronous methods, it is easy for a novice to write:

function App() {
  const [data, setData] = useState({ hits: []});// Note the position of async
  // This type of writing, although can run, but will issue a warning
  // Each async decorated function returns an implicit promise
  The useEffect function, however, has a requirement: it either returns a cleanup function or nothing at all
  useEffect(async() = > {const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',); setData(result.data); } []);return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

Copy the code

A more elegant way to write:

function App() {
  const [data, setData] = useState({ hits: []}); useEffect(() = > {
    // The more elegant way
    // Wrap the asynchronous request into a separate async function and call it in useEffect
    const fetchData = async() = > {const result = await axios(
        'https://hn.algolia.com/api/v1/search?query=redux',); setData(result.data); }; fetchData(); } []);return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

Copy the code

5. useReducer

UseReducer is similar to reducer in Redux. UseState is internally implemented by the useReducer

The useReducer receives two parameters: the Reducer function (preState and action) and the initialized state.

The return value of the useReducer is an array containing the latest state and dispatch functions (which trigger the Reducer function to calculate the corresponding state).

    // Official useReducer Demo
    // First argument: application initialization
    const initialState = {count: 0};

    // Second argument: state's reducer handler
    function reducer(state, action) {
        switch (action.type) {
            case 'increment':
              return {count: state.count + 1};
            case 'decrement':
               return {count: state.count - 1};
            default:
                throw new Error();
        }
    }

    function Counter() {
        Return values: the latest state and dispatch functions
        const [state, dispatch] = useReducer(reducer, initialState);
        return (
            <>// The useReducer will return the final state according to the dispatch action and rerender Count will be triggered: {state.count} // Dispatch receives a reducer action parameter, which triggers the reducer function and updates the reducer status<button onClick={()= > dispatch({type: 'increment'})}>+</button>
                <button onClick={()= > dispatch({type: 'decrement'})}>-</button>
            </>
        );
    }

Copy the code

Select useReducer or useState:

  • If your page state is simple, use useState directly
  • If your page state is complex (state is an object or state is scattered all over the place) use userReducer
  • For complex state operation logic (for example, an operation needs to operate or update multiple state values at the same time), you are recommended to use the useReducer for nested state objects
  • If your page component hierarchy is deep and you need child components to trigger state changes, consider useReducer + useContext

6. useContext

Receives a context object (the return value of React. CreateContext) and returns the current value of the context. The current context value is determined by the < MyContext.provider > value prop of the upper-layer component closest to the current component

When the most recent <MyContext.Provider> update is made to the component’s upper layer, the Hook triggers a rerender and uses the latest context value passed to MyContext Provider

UseContext (MyContext) corresponds to the class componentstatic contextType = MyContext or <MyContext.Consumer>

const themes = {
  light: {
    foreground: "# 000000".background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff".background: "# 222222"}};const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background.color: theme.foreground}} >
      I am styled by theme context!
    </button>
  );
}
Copy the code

Simple Redux with useContext and useReducer emulation:

The Provider component:

import React, { useReducer } from 'react'
import './App.css'
import ComponentA from './components/ComponentA'
import ComponentB from './components/ComponentB'
import ComponentC from './components/ComponentC'
const initialState = 0
const reducer = (state, action) = > {
    switch (action) {
        case 'increment':
            return state + 1
        case 'decrement':
            return state - 1
        case 'reset':
            return initialState
        default:
            return state
    }
}

export const CountContext = React.createContext()

function App() {
    const [count, dispatch] = useReducer(reducer, initialState)
    return (
        <CountContext.Provider
            value={{ countState: count.countDispatch: dispatch }}
        >
            <div className="App">
                {count}
                <ComponentA />
                <ComponentB />
                                <ComponentC />
            </div>
        </CountContext.Provider>)}export default App    
Copy the code

Component A:

/ / component A
function ComponentA() {
  const countContext = useContext(CountContext)
  return (
    <div>
      Component A {countContext.countState}
      <button onClick={()= > countContext.countDispatch('increment')}>Increment</button>
            <button onClick={()= > countContext.countDispatch('decrement')}>Decrement</button>
            <button onClick={()= > countContext.countDispatch('reset')}>Reset</button>
    </div>)}Copy the code

Component B:

function ComponentB() {
  const countContext = useContext(CountContext)
  return (
    <div>
      Component B {countContext.countState}
      <button onClick={()= > countContext.countDispatch('increment')}>Increment</button>
            <button onClick={()= > countContext.countDispatch('decrement')}>Decrement</button>
            <button onClick={()= > countContext.countDispatch('reset')}>Reset</button>
    </div>)}Copy the code

7. useRef

UseRef returns a mutable ref object whose.current property is initialized as the passed parameter (initialValue). The ref object returned remains constant throughout the life of the component

The ref object returned by useRef remains the same throughout the life of the component, meaning that the same REF object is returned each time the function component is re-rendered.

But with React. CreateRef, the ref is recreated each time the component is re-rendered

Example: Use useRef to store DOM nodes

function Child() {
    const inputRef = useRef();
    console.log('input===inputRef', input === inputRef);
    input = inputRef;
    function getFocus() {
        inputRef.current.focus();
    }
    return (
        <>
            <input type="text" ref={inputRef} />
            <button onClick={getFocus}>Get focus</button>
        </>)}Copy the code

Use useRef to update useState asynchronously

For the asynchronous update asynchronism problem discussed in useState above, use the IMmutable RefObject returned by useRef (storing values on the current property) to store state. You can think of the values stored by useRef as properties stored by this in the class component instance. Then the value changed from counter to counterref.current. As follows:

import React, { useState, useRef, useEffect } from "react";
​
const Counter = () = > {
  const [counter, setCounter] = useState(0);
  const counterRef = useRef(counter);
​
  const onAlertButtonClick = () = > {
    setTimeout(() = > {
      alert("Value: " + counterRef.current);
    }, 3000);
  };
​
  useEffect(() = > {
    counterRef.current = counter;
  });
​
  return (
    <div>
      <p>You clicked {counter} times.</p>
      <button onClick={()= > setCounter(counter + 1)}>Click me</button>
      <button onClick={onAlertButtonClick}>
        Show me the value in 3 seconds
      </button>
    </div>
  );
};
​
export default Counter;
Copy the code

React.forwardRef

Before useRef, the function component could not use the ref attribute to get a DOM reference.

The TextInput function component:

const TextInput =  forwardRef((props,ref) = > {
	// Set the input tag node node as the ref reference to the TextInput component
  return <input ref={ref}></input>
})
Copy the code
function TextInputWithFocusButton() {
  // Key code
  const inputEl = useRef(null);
  const onButtonClick = () = > {
    // Key code, 'current' points to the text input element that has been mounted to the DOM
    inputEl.current.focus();
  };
  return (
    <>// Use useRef to store the ref reference set by TextInput<TextInput ref={inputEl}></TextInput>
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
Copy the code

The above example shows that the forwardRef and useRef work together to manipulate the parent’s ref object

8. useCallback

UseCallback caches a function that is passed to a child component as props from the parent component, or that contains hooks from custom components. This function does not depend on data from the component that references it.

  • You do not need to re-declare new functions every time to avoid the waste of computing resources in freeing and allocating memory
  • Child components are not re-rendered by changes to this function. 【 Use with react. memo 】
function Example(){

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


    const getNum = useCallback(() = > {
        return (555 * 666666 )+count
        // The count is recalculated only if the count value changes
    },[count])

    const Child = React.memo(({getNum}) = >{
        return <h4>The sum of the {getNum ()}</h4>
    })

    return <div>
           <Child getNum={getNum}/>
        <button onClick={()= > setCount(count + 1)}>+1</button>
        </div>

}
Copy the code

In the example above, we pass a function to useCallBack and pass it as props to a subcomponent wrapped in Memo that calls this method, defining that the subcomponent will only be rerendered if the coutn changes

Because a function wrapped through useCallBack is always a reference to that function that is passed to the child component via props

9. useMemo

UseMemo is used to optimize the rendering process. The two parameters are the calculation function (usually a component function) and the list of dependent states, which are triggered when the state of the dependency changes. If no dependency is specified, the calculation function is executed during each rendering process.

The return value of useMemo is the return value of the computed function

function Example(){

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


    const getNum = useMemo(() = > {
        return (555 * 666666 )+count
        
        // The count is recalculated only if the count value changes
    },[count])

    return <div>
    <h4>Total: {getNum}</h4>
        <button onClick={()= > setCount(count + 1)}>+1</button>
        </div>

}

export default Example;


Copy the code

The above example only triggers the recalculation and rendering of the getNum function if the count changes; If useMemo is not used, any state change will cause the component to be re-rendered and thus getNum to recalculate, costing performance

Note:

If the return value of useMemo is passed as props to a subcomponent, then useMemo is not required when the value is of a raw type (string,number, etc.). React itself does not re-render function subcomponents as long as this value remains unchanged. If it is a reference type value, useMemo can be used because the reference changes even though its content does not change. So use useMemo to cache its references

The difference between useMemo and useCallback The useMemo and useCallback receive the same parameters. The first parameter is the callback and the second parameter is the data to depend on

Working together:

  • Only when the data changes will the result be recalculated, that is, it acts as a cache.

Differences between the two:

  • The useMemo calculation result is the value returned by the calculation function and is mainly used to cache the calculation result value. The application scenarios are as follows: The status to be calculated
  • UseCallback calculation results are calculation functions, mainly used for caching functions, application scenarios such as: functions that need to be cached, because the whole component will be refreshed every time any state changes, some functions are not necessary to be refreshed, so they should be cached to improve performance. And reduce waste of resources.

10. Custom hooks

Must custom hooks start with use?

It has to be. 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.

(1) useDidMount

import { useEffect } from 'react';

const useDidMount = fn= > useEffect(() = > fn && fn(), []);

export default useDidMount;

Copy the code

(2) useWillUnmount

UseEffect, which allows you to return a function that clears side effects, is equivalent to componentWillUnMount when the dependency is []

import { useEffect } from 'react';

const useWillUnmount = fn= > useEffect(() = > () = > fn && fn(), []);

export default useWillUnmount;

Copy the code

(3) Implement setState methods similar to class components that can support callbacks

When a class component updates its state, setState can be used as a second argument to retrieve the updated callback function. Unfortunately, the hooks function’s useState second argument callback does not support the second argument callback, although it supports usage similar to the first argument of setState for the class component (by passing in a function and updating the return value of that function as the new state). But there are many business scenarios where we want the hooks component to support the updated callback method.

Use useRef and useEffect with useState to do this:

const useXState = (initState) = > {
    const [state, setState] = useState(initState)
    // Indicates that the state value has been updated
    let isUpdate = useRef()
    const setXState = (state, cb) = > {
        // setState updates the value of useState as a function parameter, instead of the specified parameter value
        setState(prev= > {
            isUpdate.current = cb
            return typeof state === 'function' ? state(prev) : state
        })
    }
    useEffect(() = > {
        if(isUpdate.current) {
        	// If update state exists, the callback is executed
            isUpdate.current()
        }
    })

    return [state, setXState]
}

export default useXState
Copy the code

Description:

When executing setXstate, the same arguments as setState are passed and the callback is assigned to useRef’s current property so that when the update is complete, The updated callback can be implemented by manually calling current

11. Hooks vs Render Props vs HOC

Before Hooks, higher-order components and Render Props were essentially promoting reuse logic into the parent component. With Hooks, we extracted the reuse logic to the top of the component instead of pushing it to the parent component. This avoids the “nested hell” of HOC and Render Props. However, if you have a parent-child hierarchy like Context and, you can only use Render Props or HOC.

Hooks, Render Props, and higher-order components all have their own use scenarios:

Hooks:

Most use cases to replace Class, except getSnapshotBeforeUpdate and componentDidCatch are not yet supported. Extractable reuse logic. Use Hooks for all scenarios except those with explicit parent-child relationships.

Render Props:

It has a higher degree of freedom in component rendering and can render dynamically according to the data provided by the parent component. Suitable for situations where there is a clear father-son relationship.

Higher-order components:

It is suitable for injection and generates a new reusable component. Good for writing plug-ins.

However, scenarios that can use Hooks should use Hooks first, before Render Props and HOC. Of course, Hooks, Render Props, and HOC are not opposites. We can write Render Props and HOC with Hooks, and we can use Render Props and Hooks in HOC.