What are React Hooks and why are they used?

Hook is a new feature in React 16.8. The main purpose of this feature is to write function components using state, handle side effects and other React features

Before using function components, most of them are stateless. They can only use props and UI display components, and there is no logical state to inject state, etc. Using state can only use class components. However, when using class components, if a lot of business logic needs to be put in the life cycle functions, It will make the project more and more complex and difficult to maintain. This problem in the class needs to be bound, which is also a tricky problem. In order to solve these problems, Hook appeared

Hook mainly solves three kinds of problems:

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

Hook usage rules

Hooks are essentially a special class of functions, but there are two additional rules for using them:

  • A Hook can only be called on the outermost layer of a function. Do not call in loops, conditional judgments, or subfunctions
  • You can only call a Hook from the React function component or from a custom Hook. Do not call it from any other Javascript function

React built-in Hook

The basis of the hooks

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

Returns a state and a function to update the state

During the first rendering, the state returned is the same as the first parameter passed in (initialState). Of course, initialState can also be a function. During the first rendering, the function is called to calculate the initialState and is not called later

The setState function is used to update state. Receives a new state value and queues a rerender of the component; You can also accept a preState=>{… } and returns the result of the function’s execution

Note the following three points when using useState:

1) The setState method returned by useState is an asynchronous method like the setState method of the same type of component. The value of state will become a new value only after the component is updated

2) The setState returned by useState does not have the function of the class component setState to merge multiple states. If there are multiple states in the state, the other values will be updated at the same time, and the expansion operator in ES6 can be used to achieve the effect of merging

setState(prevState= > {
    // The object. assign method can also be used
    return{... prevState, ... updatedValues} })Copy the code

3) Multiple states can be created using useState in the same component

useEffect
UseEffect (didUpdate, [dependency])Copy the code

The Hook receives a function that contains an imperative and possibly side effect code

The technical term for this is a side Effect. What are side effects? Network requests and DOM operations are all side effects, and useEffect is specifically designed to deal with side effects

In class components side effects are usually handled in componentDidMount and componentDidUpdate, UseEffect is equivalent to componentDidMount, componentDidUpdate and componentWillUnmount

UseEffect contains two parameters: the run-time callback function didUpdate and an array of dependent parameters. The callback function also has a return function that performs actions such as clearing timers and unsubscribes. Typically, to prevent memory leaks, the cleanup function is executed before the component is unloaded. In addition, if the component is rendered multiple times (as it usually is), the previous effect is cleared before the next effect is executed

Dependency parameters, which are themselves an array of dependent data, are put into the array, and when the data is updated, only the callback function is executed. The whole life cycle process is as follows:

Component Mount -> Execute side effect (callback function) -> Component update -> Execute cleanup function (return function in callback function) -> Execute Side effect (callback function) -> Prepare component unload -> Execute Cleanup function (return function in callback function) -> Component unload

UseEffect is componentDidMount, componentDidUpdate and componentWillUnmount, if only simply want to mount, update, uninstall before one of the stages of execution, you can refer to the operation:

ComponentDidMount: If you want to execute only after the component is mounted, you can set the dependent array to an empty array [] so that this effect is not implemented during update

② componentWillUnmount: If you only want to execute before component unmount, you can also set the dependency parameters to an empty array []. The return function for this side effect will be executed before component unmount

(3) componentWillUpdate: Testing update is relatively trouble, only need to distinguish between updates or mount the rely on to check the data and the initial values are consistent, if the current data is consistent with the initial data that mount the stage, on the safe side, of course, compared with the previous value, if the current reliance on data dependence and the last data, shows the component hasn’t been updated

useContext
const value = useContext(MyContext)
Copy the code

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 value of

in the upper-layer component closest to the current component

When the most recent < MyContext.provider > update is made to the upper layer of the component, the Hook triggers a rerendering and uses the latest contextValue value passed to MyContextprovider

Components that call useContext are always rerendered when the context value changes. If rerendering a component is expensive, it can be optimized by using useMemo

Additional hooks

useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init)
Copy the code

This is an alternative to useState. It receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method, much like Redux

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

Returns a memoized function, which is essentially a caching function

Passing the inline callback function and an array of dependencies as arguments to useCallback returns an Memoized version of the modified callback function that 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 (such as shouldComponentUpdate)

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

useMemo
const memoizedValue = useMemo(() = > computedExpensiveValue(a, b), [a, b])
Copy the code

Returns an memoized value, the cached value

You pass in the create function and dependency array as arguments to useMemo, which recalculates memoized values only when a dependency changes. This optimization helps avoid costly calculations every time you render

Remember that functions passed into useMemo are executed during rendering. Do not perform non-rendering operations inside this function, such as side effects, which fall under the useEffect category

useRef
const refContainer = useRef(initialValue)
Copy the code

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

Here is an example of getting the props or state of the previous round:

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

  const prevCountRef = useRef();
  useEffect(() = > {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return <h1>Now: {count}, before: {prevCount}</h1>;
}
Copy the code
useImperativeHandle
useImperativeHandle(red, createHandle, [deps])
Copy the code

UseImperativeHandle allows you to customize the instance value exposed to the parent component when using a ref. In most cases, imperative code such as ref should be avoided. UseImperativeHandle should be used with the forwardRef:

function FancyInput(props, ref){
    const inputRef = useRef();
    useImperativeHandle(ref, () = >({
        focus: () = >{ inputRef.current.focus(); }}));return <input ref={inputRef} . />
}
FancyInput = forwardRef(FancyInput);
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 DOM layouts and trigger rerenders synchronously. The update schedule inside useLayoutEffect is refreshed synchronously before the browser performs the drawing

Use standard Useeffects whenever possible to avoid blocking visual updates

Customize the Hook

To reuse the state logic in a class component, use render props and higher-order components. In a Hook, you can extract the state logic into a reusable function, a custom Hook

A custom Hook is a function that starts with “use” and can call other hooks from within the function

For example, to get props and state for the previous round, you could use a custom Hook like this:

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return <h1>Now: {count}, before: {prevCount}</h1>;
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() = > {
    ref.current = value;
  });
  return ref.current;
}
Copy the code

There are several points to note when using custom hooks:

  1. Custom hooks must start with “use”
  2. Using the same Hook in two components does not share state
  3. Custom Hook fetching state is completely independent

Finally, Ryan Florence introduces the React Hook app and writes a custom Hook demo:

import Row from "./Row";
import React, { useContext, useEffect, useState } from "react";
import { ThemeContext } from "./context";
import "./index.css";

function Greeting(props) {
  const name = useFormInput("xiaoxu");
  const surname = useFormInput("fangfang");
  const theme = useContext(ThemeContext);
  const width = useWindowWidth();

  useDocumentTitle(name.value + "-" + surname.value);

  return (
    <section className={theme}>
      <Row label="Name">
        <input {. name} / >
      </Row>
      <Row label="Surname">
        <input {. surname} / >
      </Row>
      <Row label="Width">{width}</Row>
    </section>
  );
}

// Customize side effects title-hook
function useDocumentTitle(title) {
  useEffect(() = > {
    document.title = title;
  });
}

// Custom width-hook
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() = > {
    const handleResize = () = > {
      setWidth(window.innerWidth);
    };
    window.addEventListener("resize", handleResize);
    return () = > {
      window.removeEventListener("resize", handleResize);
    };
  });
  return width;
}

// Custom input-hook
function useFormInput(name) {
  const [value, setValue] = useState(name);
  function handleChange(e) {
    setValue(e.target.value);
  }
  return {
    value,
    onChange: handleChange
  };
}

export default Greeting;

Copy the code

The effect is as follows: