instructions

A lot of the content in this article is from the following two articles, I just added a point of my own understanding, if there is something difficult to understand the advice directly check the text Hook introduction, React Hooks detail explanation [nearly 1W words] + project actual practice

What is the Hook?

Hook is a special function that lets you “Hook” into React features. For example, useState is a Hook that allows you to add state to the React function component. We’ll look at other hooks later.

When will I use Hook?

If you were writing a function component and realized you needed to add some state to it, you would have to convert it to a class. Now you can use hooks in existing function components.

Use rules of Hook

  • Hooks can only be called on the outermost layer of a function. Do not call inside a loop, condition, or subfunction.
  • Hooks can only be called in React function components. Do not call it in another JavaScript function.
  • Custom Hook

Why use hooks

Making Component management easy One of the purposes of using hooks is to solve the problem that lifecycle functions in classes often contain unrelated logic that is separated into several different methods. Usually a component that contains side effects is initialized in componentDidMount, updated in componentDidUpdate when dependencies change, and cleared in componentWillUnmount when the component is to be destroyed. But the three phases, which are closely related to each other, we use the class component, but we need to do it in three different declarative periodic functions. Also, componentDidMount and componentDidUpdate tend to have the same logic. We can do these things in the same place, become more concise and clear.

Before Hooks, components shared state logic with each other. We used render props and high-level components, but now we can do this with custom Hooks.

Hooks used

useState

  • Initialize state lazily:

If the value of initialState is to be obtained by some computation, we would prefer to place the computation in a lazy initialization process

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

SomeExpensiveComputation (props) is executed every time the component is rendered (although this value is only used when useState is initialized). The somesivecomputation function will only be called during the initial render and will be ignored for subsequent renderings

const initialState = someExpensiveComputation(props); 
const [state, setState] = useState(initialState);
Copy the code

If the new state needs to be computed using the previous state, then the callback function can be passed as an argument to the setState. This callback will take the previous state and return an updated value.

setNumber(number= >number+1);
Copy the code

Note: Hook internally uses object. is to compare new/old states for equality

useEffect

By default, it is executed after the first render and after each update. If you’re familiar with React class lifecycle functions, you can think of useEffect Hook as a combination of componentDidMount, componentDidUpdate, and componentWillUnmount.

componentDidMounted

useEffect(() = > void[]);Copy the code

componentWillUnmount

useEffect(() = > fn, []);
Copy the code

componentDidUpdate

useEffect(() = > {
  
}, [dev1]);
Copy the code

Since the first argument to useEffect either returns void or returns a callback function, when we want to use async/await in useEffect, we need to do the following:

useEffect(() = > {
  const fetchData = async() = > {const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',); setData(result.data); }; fetchData(); } []);Copy the code

useReducer

When a component uses multiple useState methods at the same time, they need to be declared one at a time. A lot of states, a lot of statements. Such as:

const Avatar = ({ user, setUser }) = > {
 const [user, setUser] = useState("Cui Ran");
 const [age, setAge] = useState("18");
 const [gender, setGender] = useState("Female");
 const [city, setCity] = useState("Beijing");
 // more ...
};
Copy the code

We can solve this problem by using useReducer. UseReducer is actually a variant of useState, which solves the above-mentioned problems of multiple states and the need to use useState multiple times.

UseReducer use

const initialState = {number: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {number: state.number + 1};
    case 'decrement':
      return {number: state.number - 1};
    default:
      throw new Error();
  }
}

function Counter(){
    const [state, dispatch] = useReducer(reducer,initialState);
    
    return (
        <>
          Count: {state.number}
          <button onClick={()= > dispatch({type: 'increment'})}>+</button>
          <button onClick={()= > dispatch({type: 'decrement'})}>-</button>
        </>)}Copy the code

UseReducer internal implementation

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}
Copy the code

React ensures that the identifier of the Dispatch function is stable and does not change when the component is rerendered. This is why it is safe to omit dispatch from the list of dependencies for useEffect or useCallback.

useContext

The main application scenario of Context is that many components at different levels need to access the same data, such as theme, userInfo, etc

When we use React, we sometimes need to use global state to solve the problem of state passing across the hierarchy. We could have done that before with the React context. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = We pass state and dispatch from the Reducer via the value of the Provider. Then in each customer used you can get the state and the dispatch method that changes the state. UseReducer is a state management implementation, and useContext is used to solve cross-component and cross-level problems, so the two can be used together

const CounterContext = React.createContext();

function SubCounter(){
    const {state, dispatch} = useContext(CounterContext);
    return (
        <>
            <p>{state.number}</p>
            <button onClick={()= >dispatch({type:'ADD'})}>+</button>
        </>)}function Counter(){
    const [state, dispatch] = useReducer((reducer), initialState, () = >({number:initialState}));
    return (
        <CounterContext.Provider value={{state, dispatch}} >
            <SubCounter/>
        </CounterContext.Provider>
    )
}

ReactDOM.render(<Counter  />.document.getElementById('root'));
Copy the code

When the most recent value of <CounterContext.Provider> is updated on the component’s upper layer, the Hook triggers a rerendering, using the Context value that was passed to the CounterContext Provider last time. Even if the ancestor uses React.memo or shouldComponentUpdate, it will be rerendered when the component itself uses useContext.

  • UseContext (MyContext) is equivalent to static contextType = MyContext or < myContext.consumer >
  • UseContext (MyContext) just lets you read the value of the context and subscribe to the changes of the context. You still need to use < myContext.provider > in the upper component tree to provide the context for the lower component
  • Context is really just publish subscribe

useCallback userMemo memo

When our father components because of its own state changes and to render, will drive the subcomponents of rendering, timely subcomponents status did not change at this time, in order to solve the subcomponents non-essential rendering, we use the memo function to packaging components, so that when the components of props no change occurs, the parent component to apply colours to a drawing in time, Child components are also not rerendered.

In the demo below, every time we click the Add 1 button, the child component is rerendered because the parent component is rerendered. But it’s not really necessary to re-render the child component.

import React, { useState } from 'react';

const Child = (props) = > {
    console.log('Child components? ')
    return(
        <div>I am a child component</div>
    );
}
const Page = (props) = > {
    const [count, setCount] = useState(0);
    return (
        <>
            <button onClick={(e)= >{setCount(count+1)}}> add 1</button>
            <p>count:{count}</p>
            <Child />
        </>)}export default Page;
Copy the code

To solve this problem, wrap the child components with memo.

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

const Child = memo((props) = > {
    console.log('Child components? ')
    return(
        <div>I am a child component</div>
    );
});
Copy the code

When we wrap the Child component using the Memo higher-order functions provided by React, when the parent component is rerendered, the Child component is not rerendered

When our child component has props for reference types

const Page = (props) = > {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('the Child components');


    return (
        <>
            <button onClick={(e)= >{setCount(count+1)}}> add 1</button>
            <p>count:{count}</p>
            <Child name={name} onClick={(newName)= > setName(newName)}/>
        </>)}Copy the code

In this case, every time we click the add 1 button, the Child component executes. This is because every time we click the add 1 button, the parent component must render again, and the parent component must render again, even if we use the memo, but since the onClick property of the Child component is an inline reference value, and every time the parent component renders, The reference value must have changed, so the child component must also be updated. We need to introduce the useCallback Hooks function to solve this problem.

<Child name={name} onClick={useCallback((newName) = > setName(newName), [deps])}/>
Copy the code

This callback function is updated only if one of the dependencies changes.

UseCallback (fn, deps) equals useMemo(() => fn, deps).

Also, if props is an ordinary reference variable, when the parent component is rerendered, the child component will also be rerendered.

<ChildMemo name={{ name}} />
Copy the code

This is where useMemo Hooks are used to fix the problem. With useMemo, return the same object as the original. The second argument is a dependency. When the name changes, a new object is created

<Child name={useMemo(() = >({ name }), [name])}/>
Copy the code

UseCallback Usage scenario

  • Within a component, methods that become dependencies on other useEffect functions are recommended to be wrapped in useCallback, or written directly in the useEffect that references it. This usually happens in functions such as reset, and there may be multiple places to call this function.

  • If your function is going to be passed to a child component as props, be sure to use the useCallback wrapper. It can be very annoying for a child component if it changes the function you pass to it every time it render. It’s also not good for React rendering optimization.

UseMemo usage scenarios

  • Some computations are so expensive that we need to “remember” the return value to avoid recalculating every time we render.
  • We also need to “remember” this value because the reference to the value has changed, causing the downstream component to rerender.

useRef

The ref object returned by useRef remains the same throughout the lifetime of the component, which means that the same REF object is returned each time the function component is rerendered (with React.createref, the ref is recreated each time the component is rerendered). Conceptually, you can think of refs as an instance variable of a class.

UseRef isn’t just for managing DOM refs; it can hold any variables, and changing the.current property doesn’t cause re-rendering.

Usage scenario:

  • I want a variable to be in the component, and even if the component is rerendered, the value will not change.
  • Holding a value provided by a child component (which could be a DOM element or just a normal object)
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'the react - dom. function Child() { const inputRef = useRef(); function getFocus() { inputRef.current.focus(); } return (<>  )} ReactDOM.render(
      , document.getElementById('root'));
Copy the code
function Timer() {
  const intervalRef = useRef();

  useEffect(() = > {
    const id = setInterval(() = > {
      // ...
    });
    intervalRef.current = id;
    return () = > {
      clearInterval(intervalRef.current);
    };
  });

  // ...
}
Copy the code

forwardRef

Because the function component has no instance, it cannot accept the ref attribute like the class component. In order to accept the REF attribute like the class component, we need to wrap the function component with the forwardRef to make it accept the ref attribute. A wrapped component does not pass the ref attribute as props.

function Parent() {
    return (
        <>
         // <Child ref={xxx} />This is not going to work<Child />
            <button>+</button>
        </>)}Copy the code

ForwardRef forwardRef forwards the parent ref object to the dom element of the child, which takes props and ref as parameters

function Child(props,ref){
  return (
    <input type="text" ref={ref}/>
  )
}
Child = React.forwardRef(Child);

function Parent(){
  let [number,setNumber] = useState(0); 
  const inputRef = useRef(); / /} {current: ' '

  function getFocus(){
    inputRef.current.focus();
  }

  return (
      <>
        <Child ref={inputRef}/>
        <button onClick={()= >setNumber({number:number+1})}>+</button>
        <button onClick={getFocus}>Get focus</button>
      </>)}Copy the code

useImperativeHandle

Sometimes we want to execute some of the methods provided by the child component in the parent component. In a class component we can fetch the child component by ref and then execute the methods in the child component (which is also a class component), but in a function component we can’t do that. Because there is no this in the function component, we cannot get the method in the function’s child component. In this case, we can use useImperativeHandle with forwardRef.

The useImperativeHandle Hooks return an object that is used as the value of the parent’s current property

import React,{useState,useEffect,createRef,useRef,forwardRef,useImperativeHandle} from 'react';

function Child(props,parentRef){
    // The child component creates its own refs internally
    let focusRef = useRef();
    let inputRef = useRef();
    useImperativeHandle(parentRef,() = >(
      // This function returns an object
      // This object is used as the value of the parent's current property
      // In this way, the parent component can use multiple refs in the operation child component
        return {
            focusRef,
            inputRef,
            name:'Counter'.focus(){
                focusRef.current.focus();
            },
            changeText(text){ inputRef.current.value = text; }}});return (
        <>
            <input ref={focusRef}/>
            <input ref={inputRef}/>
        </>
    )
}
Child = forwardRef(Child);

function Parent(){
  const parentRef = useRef();//{current:''}
  function getFocus(){
    parentRef.current.focus();
    // This code is invalid because this property is not defined in the child component and is protected
    parentRef.current.addNumber(Awesome!);
    parentRef.current.changeText('<script>alert(1)</script>');
    console.log(parentRef.current.name);
  }
  return (
      <>
        <ForwardChild ref={parentRef}/>
        <button onClick={getFocus}>Get focus</button>
      </>)}Copy the code

useLayoutEffect

Its function signature is the same as useEffect, but it calls Effect synchronously after all DOM changes. You can use it to read the DOM layout and trigger rerendering synchronously. The update plan within the useLayoutEffect is synchronously refreshed before the browser performs the drawing



function LayoutEffect() {
    const [color, setColor] = useState('red');
    useLayoutEffect(() = > {
        alert(color);
    });
    useEffect(() = > {
        console.log('color', color);
    });
    return (
        <div>
            <div id="myDiv">Color: {color}</div>
            <button onClick={()= >SetColor () 'red'} > red</button>
            <button onClick={()= >SetColor (' yellow ')} > yellow</button>
            <button onClick={()= >SetColor (' blue ')} > blue</button>
        </div>
    );
}
Copy the code

Reference article:

Hook profile

+ project actual combat

React Hooks best practices

when-to-use-context