This article is the Plus version of the official website document. On the basis of the official website document (with deletion), I have added some understanding of my learning process and summarized the step pits in the process of using it. If you have time, I recommend you read through the official website again.

1 introduction

Hook is a new feature in React 16.8. It allows you to use state and other React features without writing class components.

Hooks provide a powerful and expressive way to reuse logic between components.

import React, { useState } from 'react'; Const [count, setCount] = useState(0); 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

1.1 motivation

Hooks solve all sorts of seemingly unrelated problems we’ve encountered over the years writing and maintaining thousands of components.

1.1.1 It is difficult to reuse state logic between components

React does not provide a way to attach reusable behavior to components. You may be familiar with solutions to this problem, such as providers, consumers, higher-order components, render props, and other abstraction layers. But such scenarios require reorganizing your component structure to make your code difficult to understand.

You can now use hooks to extract state logic from components so that it can be tested individually and reused. Hooks allow you to reuse state logic without modifying the component structure.

1.1.2 Complex components become difficult to understand

Each life cycle often contains some unrelated logic. For example, we might set event listener in componentDidMount and clear in componentWillUnmount. Code that is related and needs to be modified against each other is broken up, while completely unrelated code is mixed together in the same method. This can easily lead to bugs and inconsistencies in logic.

In most cases, it is not possible to break components down into smaller granularity because state logic is ubiquitous. This makes testing a bit of a challenge. This is also one of the reasons many people use React in conjunction with the state management library. But this often introduces a lot of abstraction and requires you to switch back and forth between different files, making reuse more difficult.

1.1.3 Hard to understand classes

The use of this is inevitable when using a class, and the orientation of this in JavaScript is not easy to understand. In different use scenarios, the object this points to in a method is different.

1.2 Differences between class components and function components

Strictly speaking, a class component is different from a function component. Different writing methods represent different programming methodologies. A class encapsulates data and logic. If you choose to write a class, you should write all related data and operations in the same class. In general, a function should only do one thing, and that is return a value. If you have multiple operations, each operation should be written as a separate function. Furthermore, the state of the data should be separated from the method of operation. According to this philosophy, the React function component should only do one thing: return the component’s HTML code.

Functional programming refers to operations that have nothing to do with data calculation as side effects. A function is no longer pure if it contains operations that produce side effects, and we call it impure. A pure function can contain side effects only by indirect means (that is, through other function calls).

Using hooks makes the code cleaner, and functional components are more in line with the React functional nature.

React Hook Hook Is the React function component’s side effects solution. It introduces side effects to function components. The body of a function component should only be used to return the component’s HTML code; all other operations (side effects) must be introduced via hooks.

There are many kinds of hooks because there are so many side effects. React provides specialized hooks for a variety of common operations.

  • useStateSave the state
  • useContextAccess context
  • useRefSave the reference
  • .
  • useEffectGeneric side effect hook, used when no corresponding hook can be found

Any side effect can be introduced using useEffect(), which has the following common uses

  • Fetching Data Fetching
  • Setting up a subscription
  • Changing the DOM
  • Logging output

Many of React’s ideas have influenced the industry: virtual DOM, JSX, functional programming, immutable state, unidirectional data flow, and so on. Hooks will bring a major front-end revolution.

1.3 Gradual Strategy

The React team plans to have hooks override all usage scenarios for class components, but will continue to provide support for class components. Hooks and existing code work together, and you can use them incrementally.

2 useStateSave the state

UseState similar to the this.setstate of the class component.

Function ExampleWithManyStates() {// Declare multiple state variables! Const [age, setAge] = useState(42) const [todos, setTodos] = useState([{text: }]) const [fruit, setFruit] = useState(() => 'banana') // function... }Copy the code

useState

  • The only argument is the initial state, which is passed in only for the first initialization
  • Returns the current state and the function that updates the state
  • During initial rendering, the state state returned is the same value as the first parameter initialState passed in
  • The setState function is used to update state. It receives a new state value and queues a re-rendering of the component
  • In general, variables “disappear” after the function exits, with the exception of variables in state, which are retained by React
  • React ensures that the setState function identifier is stable and does not change when the component is re-rendered

Difference between useState and this.setState

  • useStateThe new state will not be merged with the old state
  • touseStateThe state passed can be of any type, andthis.setStateIt can only be one object

2.1 Functional update

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

UseState does not automatically merge update objects. You can combine the expansion operators to merge updated objects. UseReducer is another option that is better suited for managing state objects with multiple child values.

setState(prevState => { return {... prevState, ... updatedValues}; });Copy the code

2.2 Lazy initial state

The initialState parameter is only used in the initial rendering of the component and will be ignored in subsequent renderings. If the initial state needs to be computed through complex calculations, we can pass in a function that computes and returns the initial state, which is only called during the initial render:

const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); // Return initialState is executed only once during component initialization; }); Const [state, setState] = useState(someExpensiveComputation(props));Copy the code

2.3 Skip state update

When you call the update function of the State Hook and pass in the current State, React skips rendering and effect execution of the child components.

React may still need to render the component before it can be skipped. React doesn’t unnecessarily render “deep” nodes in the component tree, so don’t worry. If you are performing expensive calculations during rendering, you can use useMemo for optimization.

2.4 Pothole avoidance Guide

The update status method returned by useState is asynchronous and the new value won’t be retrieved until the next redraw.

const [count, setCount] = useState(0); setCount(1); console.log(count); // It is 0, not 1Copy the code

Using the callback helps us get the latest state. In the following example, current => current + 1 is a bit more code than count + 1, but the result is more intuitive.

const App3 = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(current => {
      console.log(1, {count, current}); // 1 {count: 0, current: 0}
      return current + 1;
    });

    setCount(current => {
      console.log(2, {count, current}); // 2 {count: 0, current: 1}
      return current + 1;
    });
  };



  useEffect(() => {
    console.log(3, {count}); // 3 {count: 2}
  }, [count]);

  return <div onClick={handleClick}>{count}</div>;

}
Copy the code

2.5 Behavior Testing

  • SetState updates the component state, and the function component will be run2 times Reactjs.org/docs/strict…
  • SetState passes the same reference type or primitive type value and does not trigger component updates
  • setArray([...array])The contents of the ArrayItem element have not changed, but they still trigger rerendering

2.5.1 React.FunctionComponent vs React.PureComponent

When the parent component is updated, if the props of the child component is not changed

  • FunctionComponent always executes
  • The PureComponentrenderMethod is not executed, but is provided by defaultshouldComponentUpdateIt will still be implemented.

PureComponent performs better than PureComponent. This slight performance difference is ignored most of the time, but can be optimized in critical situations. Of course, using react.Memo () to wrap the FunctionComponent in a layer gets the same optimization.

Note 1: shouldComponentUpdate does not exist and is not allowed in PureComponnet. The actual code looks like this. There are only two places in React to compare whether a ClassComponent needs to be updated. ShouldComponentUpdate (); shouldComponentUpdate (); shouldComponentUpdate ();

if (ctor.prototype && ctor.prototype.isPureReactComponent) { return ( ! shallowEqual(oldProps, newProps) || ! shallowEqual(oldState, newState) ); }Copy the code

3 useEffectAdd side effects

You’ve probably done fetch, subscribe, or manually modify the DOM in the React component before. We collectively refer to these operations as “side effects,” or simply “effects.”

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, but has been consolidated into an API ๐Ÿ‘๐Ÿ‘.

import React, { useState, useEffect } from 'react' function Example() { const [count, SetCount] = useState(0) setCount = useState(0) Document. title = 'You clicked ${count} times'}) return (<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> <button onClick={() => setCount(c => c + 1)}>Click me</button> // setCount </div>Copy the code

When you call useEffect, you’re telling React to run your “side effect” function after making changes to the DOM. In a class component, the Render function should not have any side effects. In general, it’s too early to perform operations here, so we usually put side effects from class components in componentDidMount and componentDidUpdate.

Because side functions are declared inside the component, they can access the props and state of the component.

By default, React calls side effects after every render — including the first one.

Side effects functions can also specify how to “clear” side effects by returning a function.

UseEffect is guaranteed to be executed before any new rendering, although it is delayed after the browser has drawn. React will refresh the effect of the last render before the component is updated.

Why is useEffect called inside a component?

Placing useEffect inside the component allows us to access the props or state variables directly in effect. We don’t need a special API to read it, it’s already stored in function scope. Hooks make use of JavaScript’s closure mechanism.

Will useEffect be executed after every render?

Yes, by default, it is executed after the first render and after every update. You might be more comfortable with the idea that an effect happens “after rendering,” without having to worry about “mount” or “update.”

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.

Class Example extends Component {constructor(props) {super(props) this.state = {count: constructor(props); 0 } } componentDidMount() { document.title = `You clicked ${this.state.count} times` // 1 } componentDidUpdate() { document.title = `You clicked ${this.state.count} times` // 2 } render() { /* ... * /}}Copy the code
Function Example() {const [count, const [count, const [count, const [count, const] setCount] = useState(0) useEffect(() => { document.title = `You clicked ${count} times`; }) return ( /* ... * /)}Copy the code

3.1 Use multiple effects to achieve separation of concerns

Hooks allow us to separate code according to its purpose, rather than life cycle functions. React calls each effect in the order in which it was declared.

When using useEffect, it is important to note that if there are multiple side effects, multiple useeffects should be called and not written together.

class FriendStatusWithCounter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0, isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }


  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }



  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }



  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }
  // ...
}
Copy the code
function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

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

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);  

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

3.2 Effects to be cleared

There are also side effects that need to be removed. Such as subscribing to external data sources. In this case, cleaning is very important to prevent memory leaks! Each effect can return a cleanup function. This puts the logic for adding and removing subscriptions together.

React cleans up the previous effect before executing the current effect each time it renders. Finally, React performs another cleanup operation when the component is uninstalled.

import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } / / add side effects ChatAPI. SubscribeToFriendStatus (props. Friend. Id, handleStatusChange); // Specify how to "clear" side effects by returning a function:  return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading... '; } return isOnline ? 'Online' : 'Offline'; }Copy the code

Note: The useEffect cleanup function runs after the new rendering is complete, but before the new side effect function is restarted.

React only runs the effects after letting the browser paint. This makes your app faster as most effects donโ€™t need to block screen updates. Effect cleanup is also delayed. The previous effect is cleaned up after the re-render with new props.

function Foo() { const [count, setCount] = useState(0); if (count < 1) { setCount(count + 1); } console.log(`${count}-1`); useEffect(() => { console.log(`${count}-2`); return () => console.log(`${count}-3`); }) return <div>Foo</div>; } // Output 0-1, 1-1, 0-1, 1-1, 1-2 // output 1-1, 1-1, 1-3 // the browser has been re-rendering completed, re-rendering will be completed after the last side effect of 1-2 special note: StrictMode + test results See below under non StrictMode output content See HTTP: / / https://github.com/facebook/react/issues/15074#issuecomment-471197572 / / initialization time 0-1 1-1-1/2 / When rerendering, output 1-1, 1-3, 1-2Copy the code

Why does Effect run with every update

Experienced JavaScript developers may notice that the function passed to useEffect is different on every render, which is intentional. In fact, this is why we can get the latest count value in Effect without worrying about its expiration. Every time we re-render, a new effect is generated, replacing the previous one. In a sense, an effect is more like a part of a render result — each effect “belongs” to a particular render.

Elements will not be destroyed and rebuilt if a prop value is changed, so it is necessary to add update logic to componentDidUpdate. Forgetting to properly handle componentDidUpdate is a common source of bugs in React applications. With Effect Hook, it cleans up the previous Effect before calling a new one. This default behavior ensures consistency and avoids common bugs in class components that do not handle update logic.

class FriendStatusWithCounter extends React.Component { // ... componentDidMount() { ChatAPI.subscribeToFriendStatus(this.props.friend.id, this.handleStatusChange); } // If there is no logic here, then when friend changes, ComponentDidUpdate (prevProps) {// Unsubscribe from friend.id ChatAPI.unsubscribeFromFriendStatus(prevProps.friend.id, this.handleStatusChange); / / subscribe to new friend. Id ChatAPI. SubscribeToFriendStatus (this. Props. Friend. Id, enclosing handleStatusChange); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus(this.props.friend.id, this.handleStatusChange); } / /... }Copy the code

Here is the Hook

function FriendStatus(props) {
  // ...
  useEffect(() => {
    // ...
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}
Copy the code

3.4 Skip Effect to optimize performance

In some cases, performing cleanup or effect after every render can cause performance problems. In class components, we can resolve this by adding comparison logic to prevProps or prevState in componentDidUpdate: prevProps

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}
Copy the code

This is a common requirement, so it is built into the Hook API for useEffect. If certain values don’t change between rerenderers, you can tell React to skip the effect call by passing an array as the second optional argument to useEffect:

UseEffect (() => {document.title = 'You clicked ${count} times'; }, [count]); // Update only when count changes // const [obj, setObj] = useState({count: const [obj, setObj] = useState({count: // const ref = useRef({})Copy the code

If you want to execute effect only 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 you pass an empty array [], the props and state inside effect will always have their initial values.

In addition, remember that React waits for the browser to finish rendering the screen before delaying the useEffect call, thus making extra operations convenient.

3.5 Pothole avoidance Guide

In section 3.4 it was written that “if you pass an empty array [], the props and state inside effect will always have their initial values.” In the actual coding process, it is found that it is easy to habitually add the second parameter of useEffect, but often forget to add the internal dependency, resulting in the internal value of the function does not conform to the (mental) expectation. The React Hook dependency problem can be solved by configuring ESLint’s autofix.

useEffect(() => { console.log(a); // The reference to a here is console.log(b) as expected; }, [a]);}, [a]); // It should be [a, b], but it is easy to miss b in actual codingCopy the code

4 useContextAccess context

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 value prop of the upper-layer component

that is closest to the current component.

This Hook triggers rerendering when the most recent < myContext. Provider> update is made to the upper layer of the component. If rerendering components is expensive, you can optimize them by using Memoization. If you’re already familiar with the Context API before you hit the Hook, it should make sense, UseContext (MyContext) is equivalent to static contextType = MyContext or < myContext.consumer > in the class component.

Special note:

  • useContext(MyContext)It just allows you to read the value of the context and subscribe to changes in the context. You still need to use it in the upper component tree<MyContext.Provider>To provide the context for the underlying component.
  • Be careful not to abuse Context, as it can break your component independence.
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

5 extra hooks

Some of the hooks described below are variations on the basic hooks in the previous section, and some are only used in special cases.

5.1 useRef

  • Gets a handle to a child component or DOM node. Cannot get the ref of a function’s child component (functional components can, howeverReact.forwardRefTo pass ref), must be a class component, so class cannot be completely replaced yet
  • Storage of shared data between render cycles. State can also be saved across render cycles, but triggers re-rendering, whereas ref does not
  • The RefcurrentThe value of the Ref object can be modified at will, but the Ref object itself cannot be extendedObject.isExtensible(ref) === false

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

You should be familiar with ref, the main way to access the DOM. If you pass the ref object to the component as

, React sets the ref object’s current property to the corresponding DOM node, regardless of how the node changes.

However, useRef() is more useful than the ref attribute. It makes it easy to store any mutable value, similar to the way instance fields are used in class.

This is because it creates a normal Javascript object. UseRef () and create a {current:… The only difference with the} object is that useRef returns the same ref object every time it renders.

Remember that useRef does not notify you when the ref object contents change. Changing the current property does not cause the component to rerender. If you want to run some code when React binds or unbinds the REF of a DOM node, you need to use the ref callback.

function TextInputWithFocusButton() { const inputEl = useRef(null); Const onButtonClick = () => {// 'current' points to the text input element inputel.current.focus () mounted to the DOM; }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }Copy the code

5.1.1 ref callback

React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts.

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  const refCallback = el => {
    console.log('refCallback', {el});
    inputEl.current = el;
  }

  return (
    <>
      <input ref={refCallback} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
Copy the code

5.2 useMemo

Memo() limits whether a component is rendered repeatedly, while useMemo() limits whether a function is executed repeatedly. The second argument logic for useMemo() and useEffect() is the same, except that useMemo has a return value and is executed before rendering, whereas useEffect is executed after rendering. 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. Remember that functions passed into useMemo are executed during rendering. Please do not perform non-rendering operations inside this function. Operations such as side effects are used by useEffect, not useMemo. If the dependency array is not provided, useMemo evaluates the new value each time it renders.

// This is a lot like computed in vue.js const double = useMemo(() => count * 2, [count])Copy the code

5.3 useCallback

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).

// The memo is a HOC that converts' Component 'or' FunctionComponent 'to a' PureComponent '// In this case, If the count value in the App changes, it will not output "Foo render", Const Foo = memo(function Foo(props) {console.log('Foo render') // This must be explicitly bound. This is not the same as vue.js // it can also be written as {... Return <div onClick={props. OnClick}>Me Foo</div>}) const App = () => {const [count, SetCount] = useState(0) Const clickFoo = useCallback(() => console.log('Foo Clicked'), []) return (<div> <button onClick={() => setCount(count + 1)}>Add</button OnClick ={clickFoo} /> // to pass to a subcomponent, useCallback </div>)}Copy the code

5.3.1 Pothole Avoidance Guide

UseMemo and useCallback are hooks that focus on performance optimization, but they can easily be overkill for beginners: UseMemo (XXX, dependencies) has several dependencies, and the dependencies in useMemo(XXX, dependencies) do not include dependencies, so that the dependencies do not get the latest value. So I recommend that newbies use these hooks only when they have performance issues, not if they have to.

5.4 useReducer

UseReducer may be more useful than useState in some situations, such as when the state logic is complex and contains multiple subvalues, or when the next state depends on the previous state. Also, using useReducer can optimize performance for components that trigger deep updates because you can pass dispatches to child components instead of callbacks.

  • Redux: Global state management for user, auth, etc.

  • useReducer: Complex local state where dispatch is often passed to children as well.

  • useState: Simple local state where the setter is seldom passed to children.

I use all of the above.

  • Global shared status for easy debugging and maintenance with Redux
  • Simple component stateuseState
  • For complex component states that require multiple types of operations or need to pass setters to child componentsuseReducer. Used especially when different subcomponents need to perform different operations on a complex statedispatchYou can make the intent of the child components more explicit.
const initialState = {count: 0};
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() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
Copy the code

There are two ways to initialize state

  • One is direct incominguseReducer(reducer, initialState)
  • One is dynamic initialization.useReducer(reducer, initialArg, init)

You can also create the initial state lazily. To do this, you can pass an init function as the third argument. The initial state will be set to init(initialArg).

It lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action:

function init(initialCount) {
  return {count: initialCount};
}


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



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

5.1.1 Pothole Avoidance Guide

UseReducer has a similar problem with useState. No matter how many times you call dispatch({type: ‘a’, payload: A + 1}), the results are the same. At this time, we can support the use of callback by modifying reducer, as shown in the example below.

Const reducer = (state, action) => {// Support similar to dispatch(state => ({type: 'a', payload: Typeof action === 'function') {action = action(state); typeof action === 'function'; } // Support similar to dispatch({type: 'a', payload: State => state.a + 1})) if (typeof action.payload === 'function') {action.payload = action.payload(state); } console.log({state, action}); if (action.type === 'a') { return { ... state, a: action.payload }; } else { return state; } } const App = () => { const [state, dispatch] = useReducer(reducer, {a: 0}) const handleClick = () => { dispatch({type: 'a', payload: state.a + 1}); dispatch({type: 'a', payload: state.a + 1}); / / dispatch the pit ({type: 'a', content: s = > s.a + 1}); / / from the pit}; useEffect(() => { console.log('state in useEffect', state); }) return <div onClick={handleClick}>Dispatch</div>; }Copy the code

5.5 useImperativeHandle

UseImperativeHandle allows you to customize the instance value exposed to the parent component when using a ref (typically passing func up). Such imperative code should be avoided as much as possible. UseImperativeHandle needs to work with the forwardRef:

function FancyInput(props, ref) {
  const inputRef = useRef()
  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus()
  }))
  return <input ref={inputRef} />;
}

FancyInput = forwardRef(FancyInput);

function Foo () {
  const fancyInputRef = useRef(null)
  return (
    <>
      <span onClick={() => fancyInputRef.current.focus()}></span>
      <FancyInput ref={fancyInputRef} />
    </>
  )
}
Copy the code

5.6 useLayoutEffect

It fires synchronously after all DOM mutations. We recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.

Your code runs immediately after the DOM has been updated, but before the browser has had a chance to paint those changes (the user doesn’t actually see the updates until after the browser has repainted).

Its function signature is the same as useEffect, but it calls Effect synchronously (that is, blocking) 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.

UseLayoutEffect and componentDidMount, componentDidUpdate call phase is the same.

UseLayoutEffect blocks the main thread of the browser and any changes made in it are reflected in the next rendering. UseEffect gives up the main thread and adds the task to the event queue for execution. DevTools/Performance/Main Task

If you use server-side rendering…

5.7 useDebugValue

UseDebugValue can be used to display tags for custom hooks in the React developer tool. In some cases, formatting the display of values can be an expensive operation. There is no need to do this unless you need to check the Hook. Therefore, useDebugValue accepts a formatting function as an optional second argument. This function is called only when the Hook is checked. It takes a debug value as an argument and returns a formatted display value.

6 Custom hooks

So far, there are two popular ways to share state logic between components in React: Render props and higher-order components. Now let’s look at how hooks can solve the same problem without letting you add components.

A custom Hook is a function whose name starts with “use” that can call other hooks from within.

As in the component, make sure that other hooks are called unconditionally only at the top level of your custom Hook.

Unlike the React component, custom hooks do not need to have a special identity. We are free to decide what its arguments are and what it should return (if necessary).

Custom hooks are a convention that naturally follows Hook design, not a Feature of React.

You can create custom hooks that cover a variety of scenarios, such as form processing, animation, subscription declarations, timers, and maybe even more that we didn’t think of.

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.

Will using the same Hook in two components share state?

Don’t. 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.

import React, { useState, useEffect } from 'react'; UseFriendStatus (friendID) {const [isOnline, setIsOnline] = useState(null); // Custom hook 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; } // function FriendStatus(props) {const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading... '; } return isOnline ? 'Online' : 'Offline'; } // function FriendListItem(props) {const isOnline = useFriendStatus(props.friend.id); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }Copy the code

6.1 usePrevious

Get props or state for the previous round. Given that this is a relatively common usage scenario, it is likely that React will have this Hook built in in the future.

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

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

6.2 useForceUpdate

Used to force component updates, usually when useRef is used to manage mutable state but requires rerendering.

function useForceUpdate() {
  const [, forceUpdate] = useReducer(v => v + 1, 0);
  return forceUpdate;
}



function Demo() {
  const counter = useRef(0);
  const forceUpdate = useForceUpdate();
  const handleClick = () => {
    counter.current++;
    forceUpdate();
  }
  return <div onClick={handleClick}>{counter.current}</div>;
}
Copy the code

6.3 Using third-party Libraries

React hooks are very basic, and much of the logic in the actual business can be reused. It is highly recommended to use the third-party hook library to improve the effectiveness. The internal byte project is not open source yet, so I recommend Ahooks.js.org/

7 Hook rules

React requires you to follow two rules when using hooks, which are limited by the current underlying implementation of hooks, but may not be in the future. Not do not want to, is temporarily unable to achieve. Some of the potholes mentioned above are due to the current design or implementation of the Hook. Hook has been great, but the use of the threshold is not, or is still far from perfect. Hooks are JavaScript functions, but there are two additional rules for using them.

7.1 Hook only at the top level

Never call hooks in loops, conditions, or nested functions. Make sure you always call them at the top of your React function. By following this rule, you can ensure that hooks are called in the same order every time you render. This allows React to keep the hook state correct between multiple useState and useEffect calls.

7.2 Only call a Hook in a function component

Never call a Hook in a normal JavaScript function. You can:

  • Call a Hook in the React function component
  • Call other hooks in custom hooks

Follow this rule to ensure that the component’s state logic is clearly visible in your code.

7.3 Why are there these two rules

How does React associate Hook calls with components?

When we use multiple State or Effect hooks in a single component, React throws the hooks into an array. React relies on the order in which the hooks are called to distinguish each Hook.

If we want to conditionally execute an effect, we can put the judgment inside the Hook:

UseEffect (function persistForm() {if (name! == '') { localStorage.setItem('formData', name); }});Copy the code