What is the Hooks

Hook is a new feature in React 16.8.

Hooks are essentially a special class of functions that inject special functionality into your function components, allowing you to use state and other React features without writing classes.

Why use React Hooks

  • Difficult to reuse state logic: As the business becomes more complex, the sharing of state between components becomes more frequent, making component reuse and state logic management very complicated. Using Redux also increases the complexity and size of your project.
  • Composition is complex and difficult to maintain: Complex components have all sorts of unmanageable states and side effects, and you can write all sorts of unrelated logic for different situations in the same lifecycle, but in reality we usually want a function to do one thing.
  • Class this directivity problem:When we create the React component using class, we often write code like this to ensure that this points correctly:const that = this, or thethis.handleClick = this.handleClick.bind(this)>; Once this is used incorrectly, all kinds of bugs can follow.

To solve these problems, hooks allow us to implement various functions of class using simple special functions.

useState

In the React component, we often use state to respond to data in real time, and re-render the component to update the view based on state changes.

Because pure functions cannot have state, in hooks, useState is a state hook used to introduce state to function components.

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

The only argument to useState is initial state, which returns an array where item [0] is the current state value and item [1] is the method function that can change the state value.

Lazy initialization

The initialState parameter is the state used during initial rendering. It will be ignored in subsequent renderings. If the initial state is the result of an expensive calculation, you can instead provide a function that is executed only at initial render time:

function Counter({initialCount = 0}) {
  // The initial value is 1
  const [count, setCount] = useState(() = > initialCount + 1);
  return (
    <>
      Count: {count}
      <button onClick={()= > setCount(0)}>Reset</button>
      <button onClick={()= > setCount(count + 1)}>+</button>
      <button onClick={()= > setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}
Copy the code

Functional updates versus regular updates

If you need to calculate the new state using the previous state, you can pass the function to setState. This function accepts the value of the previous state and returns the updated state.

SetCount (newCount) and setCount(preCount => newCount)

function Counter() {
  const [count, setCount] = useState(0);
  function add() {
    setTimeout(() = > {
      setCount(count + 1);
    }, 3000);
  }
  function preAdd(){
    setTimeout(() = > {
      // Set a new count based on the previous count
      setCount(count= > count + 1);
    }, 3000);
  }
  // listen for count changes
  useEffect(() = > {
    console.log(count)
  }, [count])
  return (
    <>
      Count: {count}
      <button onClick={add}>add</button>
      <button onClick={preAdd}>preAdd</button>
    </>
  );
}
Copy the code

We first hit the Add button three times quickly, and after three seconds count is 1; Then click preAdd three times quickly, and three seconds later, 2, 3, and 4 appear. The test results are as follows:

Why setCount(count + 1) seems to be executed only once is because each update is a separate closure, and the function component is called again when the update status is clicked. For a quick click, the current count is 0, that is, the value passed in each click is the same, so the result is the same, and finally the count changes to 1.

Why setCount(count => count + 1) seems to work three times is that when a function is passed in, the callback will receive the current state and return an updated value. After three seconds, the first setCount gets the latest count of 1, and the second setCount gets the current count of 2, and the second setCount gets the current count of 2, and the second setCount gets the current count of 3. Each time the latest count is different, the final result is naturally different.

So for the second experiment, I clicked preAdd three times quickly, then clicked add three times quickly, and then what happened three seconds later. PreAdd = 1, count = 1, count = 2, count = 3, count = 0, count = 1 The final result should be 1, 2, 3, 1. The test result is correct:

useReducer

const [state, dispatch] = useReducer(reducer, initialState, initialFunc);
Copy the code

The useReducer can accept three parameters. The first parameter receives a reducer function of the form (state, action) => newState, which can be used to modify the related logic through dispatch(action).

The second argument is the initial value of state, which returns the current state and the dispatch function that sent the action.

You can choose to create the initial state lazilyto do so by passing in the init function as the third argument to the useReducer so that the initial state will be set to init(initialArg).

Advantages over useState

UseReducer is an advanced Hook provided by React. It is not necessary like useEffect and useState hooks. What are the benefits of using useReducer? Use useReducer to rewrite the counter example:

// Official example
function countReducer(state, action) {
  switch (action.type) {
    case 'add':
      return state + 1;
    case 'minus':
      return state - 1;
    default:
      returnstate; }}Copy the code
function initFunc(initialCount) {
  return initialCount + 1;
}
Copy the code
function Counter({initialCount = 0}) {
  const [count, dispatch] = useReducer(countReducer, initialCount, initFunc);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={()= >{ dispatch({ type: 'add' }); }} > Click +1</button>
      <button onClick={()= >{ dispatch({ type: 'minus' }); }} > Click -1</button>
    </div>
  );
}
Copy the code

Compared to useState, it may seem like our code is getting more complex, but applying state management and code logic together in a complex project environment makes our code more readable, maintainable, and predictable.

useEffect

useEffect(create, deps);
Copy the code

UseEffect () is used to introduce operations that have side effects, most commonly requesting data from the server. The Hook receives a function that is executed after the component has been rendered to the screen.

UseEffect Hook can be used as a combination of componentDidMount, componentDidUpdate, and componentWillUnmount compared to the React class life cycle. By default, react first renders and every subsequent render calls the function passed to useEffect.

UseEffect performance problems

Because React first renders and every subsequent render calls the function passed to useEffect, performance issues are likely in most cases.

To solve this problem, you can pass the array as an optional second argument to useEffect. The array can optionally write the data in state, which means that the statements in the function are executed only when the state in the array changes. In this way, multiple Useeffects can be used to separate function concerns. If it is an empty array, it is executed only once, similar to componentDidUpdata.

Unbinding side effect

In the React class, there are often things you need to do when a component is uninstalled, such as removing listening events. In the class component we can do this in the componentWillUnmount lifecycle, whereas in the hooks we can do the same by returning a function in the first useEffect function. Here is a simple clear timer example:

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

  useEffect(() = > {
    const timer = setInterval(() = > {
      setCount(count= > count + 1);
    }, 1000);
    return () = > clearInterval(timer); } []);return (
    <>
      Count: {count}
    </>
  );
}
Copy the code

useLayoutEffect

useLayoutEffect(create, deps);
Copy the code

It has the same structure as useEffect except when it is called.

  • UseEffect is executed asynchronously at rendering time, not until the browser has rendered all changes to the screen.
  • UseLayoutEffect is executed after the browser layout, before the painting,
  • You can use useLayoutEffect to read the DOM layout and trigger the rerender synchronously
  • Use the standard useEffect whenever possible to avoid blocking view updates

Difference between useEffect and useEffect

To make a clear contrast between useEffect and useEffect, let’s write a demo to see the effects of the two hooks:

function Counter() {
  function delay(ms){
    const startTime = new Date().getTime();
    while (new Date().getTime() < startTime + ms);
  }
  const [count, setCount] = useState(0);

  // useLayoutEffect(() => {
  // console.log('useLayoutEffect:', count)
  // return () => console.log('useLayoutEffectDestory:', count)
  // }, [count]);

  useEffect(() = > {
    console.log('useEffect:', count)
    // Extend for one second to see the effect
    if(count === 5) {
      delay(1000)
      setCount(count= > count + 1)}return () = > console.log('useEffectDestory:', count)
  }, [count]);

  return (
    <>
      Count: {count}
      <button onClick={()= > setCount(5)}>set</button>
    </>
  );
}
Copy the code

UseEffect: useEffect:

UseEffectDestroy = 0, useEffectDestroy = 5, useEffectDestroy = 6, useEffectDestroy = 6 Then render 6.

In the whole rendering process, count 0->5->6 can be clearly seen. In the actual project, this situation will have a flash screen effect, which will greatly affect the user experience. Because useEffect is rendered asynchronously and not executed until the browser renders all changes to the screen, we try to avoid DOM manipulation in useEffect.

Then put the setCount operation into useLayoutEffect to see what happens:

UseLayoutEffect and useLayoutEffectDestroy are executed in the same order as useEffect,Are destroyed before the next operationBut the whole rendering process is obviously different from useEffect. Although there is a noticeable pause in the printed useLayoutEffect, only count 0->6 can be seen during rendering,This is because of the synchronization feature of useLayoutEffect, which synchronizes and updates DOM data before rendering by the browser. Even multiple operations will be processed once before rendering and then handed over to the browser for rendering. This will not cause a splash screen, but will block view updates..

Finally, we look at the execution timing of the two setCouts in the two hooks at the same time.

UseEffect executes the effect:

Effect of useLayoutEffect execution:

We can see that no matter where setCount is executed, the sequence of hooks remains the same: useLayoutEffect is destroyed, then useLayoutEffect is executed, then useEffect is destroyed, then useEffect is executed. But with the difference in page rendering and the obvious lag in printing, we know that hooks should be executed when useLayoutEffect story -> useLayoutEffect -> render -> useEffectDestory -> useEffect.

useMemo

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

You pass in the create function and the 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.

Difference between useMemo and useEffect

UseMemo looks a lot like useEffect, but if you want to use setCount or other DOM modification operations in useMemo, you may run into some problems. Since the functions passed into useMemo are executed during rendering, you may not see the desired effect, so please do not perform operations inside this function that are not related to rendering.

UseMemo also returns an Memoized value, which is recalculated only when a dependency changes. This optimization helps avoid costly calculations every time you render, as shown in the following example:

function Counter() {
  const [count, setCount] = useState(1);
  const [val, setValue] = useState(' ');

  const getNum = () = > {
    console.log('compute');
    let sum = 0;
    for (let i = 0; i < count * 100; i++) {
      sum += i;
    }
    return sum;
  }

  const memoNum = useMemo(() = > getNum(), [count])

  return <div>
    <h4>Total: {getNum()} {memoNum}</h4>
    <div>
      <button onClick={()= > setCount(count + 1)}>+1</button>
      <input value={val} onChange={event= > setValue(event.target.value)}/>
    </div>
  </div>;
}
Copy the code

UseMemo effect:

Normally, when you type in the input box, the page will be re-rendered because you changed val, and then you will need to recalculate getNum, but with useMemo, memoNum will not recalculate because the dependent count is the same.

useCallback

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 (such as shouldComponentUpdate).

UseCallback (fn, deps) is equivalent to useMemo(() => FN, deps).

useRef

const refContainer = useRef(initialValue);
Copy the code
  • CreateRef is used for class components and React elements, and useRef is used for function components
  • UseRef returns a mutable REF object whose current attribute is initialized to the passed parameter (initialValue)
  • The ref object returned by useRef remains the same throughout the life of the component, meaning that the ref object returned is the same each time the function component is re-rendered (using react.createref, refs are recreated each time the component is re-rendered).
// Examples from the official website
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () = > {
    // 'current' points to the text input element mounted to the DOM
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
Copy the code

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])
Copy the code

UseImperativeHandle allows you to customize the instance value exposed to the parent component when using a ref.

As follows, the parent component rendering
can call inputref.current.focus () :

// Examples from the official website
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () = > ({
    focus: () = >{ inputRef.current.focus(); }}));return <input ref={inputRef} . />;
}
FancyInput = forwardRef(FancyInput);
Copy the code

useContext

In hooks, components are functions, so we can pass values as arguments, but sometimes we have sibling components and parent components, so passing values as function arguments is not convenient. Hooks provide useContext (shared state hook) to solve this problem.

UseContext takes a context object (the value returned from react. createContext) and returns the current context value, given to the context by the latest context provider.

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

To use content in hooks, use createContext, useContext:

// context.js creates a new context
import { createContext } from 'react';
const AppContext = React.createContext({});
Copy the code
// hooksContext. JSX parent, which provides the context
import React, { useState } from 'react';
import AppContext from './context';

function HooksContext() {
  const [count, setCnt] = useState(0);
  const [age, setAge] = useState(16);

  return (
    <div>
      <p>{age age}</p>
      <p>You hit {count} times</p>
      <AppContext.Provider value={{ count.age}} >
        <div className="App">
          <Navbar />
          <Messages />
        </div>
      </AppContext.Provider>
    </div>
  );
}
Copy the code
// Subcomponent, using context
import React, { useContext } from 'react';
import AppContext from './context';

const Navbar = () = > {
  const { count, age } = useContext(AppContext);
  return (
    <div className="navbar">
      <p>Use the context</p>
      <p>{age age}</p>
      <p>Click {count} times</p>
    </div>
  );
}
Copy the code

Build custom hooks

When we want to share logic between two JavaScript functions, we extract the shared logic into a third function. Both components and hooks are functions, so you can call other hooks this way.

For example, we can separate the function of checking whether friends are online and create a new hook useFriendStatus to check whether a certain ID is online:

// Examples from the official website
import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() = > {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () = > {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
Copy the code

At this point we can do whatever we want with the FriendStatus component:

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading... ';
  }
  return isOnline ? 'Online' : 'Offline';
}
Copy the code
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}
Copy the code

Simple summary

hook function
useState Set and change state instead of state and setState
useReducer Replace reducer in redux to facilitate the management of state logic
useEffect Introduce operations with side effects, analogous to the original lifecycle
useLayoutEffect It has the same effect as useEffect, but calls effect synchronously
useMemo It can be executed according to the state change control method to optimize useless rendering and improve performance
useCallback Similar to useMemo, useMemo optimizes the pass-through method and USecallBack optimizes the pass-through method
useContext Context parent components and deeper components pass values
useRef Returns a mutable ref object
useImperativeHandle Allows you to customize the instance values exposed to the parent component when using ref

Refer to the article

React Hooks

React Hooks Tutorial – Ruan Yifeng

React Hooks 【 nearly 1W words 】+ project combat

The welfare of

Article in “big zhuan FE” public number will also be sent, and the public number has a lucky draw, this prize is a memorial T-shirt, welcome to pay attention to (°▽°) Blue ✿