Hook

Know React Hook, this is enough.

React Hook 9 React Hook 9 React Hook 9 React Hook

useState

UseState is similar to this.setState in the Class Component.

When we define the state variable with useState, it returns an array of two values. The first value is the current state, and the second value is the function that updates the state.

Closures generated by State

For the closure generated by useState, let’s look at this code:

import React, { useState } from 'react'

const DemoState: React.FC = () = > {
  const [count, setCount] = useState(0)

  const handleClickBtn = () = > {
    setCount(count + 1)}const handleAlert = () = > {
    setTimeout(() = > {
      alert(count)
    }, 3000)}return <div>
    <div style={{ margin: '20px' }}>
      <button onClick={handleClickBtn}>Click Me !</button>
    </div>
    <p onClick={handleAlert}>This is Count: {count}</p>
  </div>
}

export {
  DemoState
}
Copy the code

The page will render:

  • A Button, and when we click on a Button count is incremented by one.

  • A p tag, and when we click on the P tag the timer will print out the value of count three seconds later.

Let’s do something like this:

Click the P TAB and Click three times quickly Click Me! After. At this point, the value of count has been updated to 3 on the page, but it will still print 0 in setTimeout after 3s.

Each render function has its own props and state. In JSX, when we call the state in the code to render, Each render gets props and state in its own render scope.

So it’s not surprising that when the timer fires, the count is taken because the closure is the internal count value of the DemoState function when it was first rendered, and alert is 0.

If you’re still confused about the basics here, take a closer look at useRef in React.

The so-called batch update principle

Students familiar with React know that the so-called state changes follow the batch update principle inside React.

The so-called asynchronous batch means that if multiple state changes are involved in a page update, the results of multiple state changes will be merged to get the final result and then a page update will be carried out.

The bulk updating principle is also only enabled in the synthesis event by enabling the isBatchUpdating state. Simply put”

  1. Where React can be controlled, it is asynchronous batch updates. Such as event functions, lifecycle functions, synchronization code within components.
  2. Anyone whoReactCan not control the place, is synchronous batch update. Such assetTimeout.setInterval.Source raw DOMEvent,includingPromiseIn theAll are synchronized batch updates.

React 18 uses createRoot to batch process external event handlers. In other words, the latest React setTimeout, setInterval and other things that cannot be controlled are changed to batch update.

For compositing events and batch updates, you can dig deeper into the React state article.

Implement useSetState

SetState is supported in Class Component this.setState and overridden in useState if object is passed.

If we need to implement this requirement in useState, we can do so by encapsulating an additional useSetState Hook:

import { useCallback, useState } from 'react';

function useSetState<T extends {}>(
  initialState: T = {} as T
): [T, (patch: Partial<T> | ((prevState: T) => Partial<T>)) = > void] {
  const [state, set] = useState<T>(initialState);

  const setState = useCallback((patch) = > {
    set((preState) = >
      Object.assign(
        {},
        preState,
        typeof patch === 'function'? patch(preState) : patch ) ); } []);return [state, setState];
}

export default useSetState;
Copy the code

You can click on the CodeSanBox address to see an example.

useEffect

UseEffect is called a side effect Hook, and this Hook is as basic as useState. Effect Hook allows you to perform side effects in function components.

UseEffect Hook supports two arguments. The first argument is a function representing the side effect function, which is executed by default after the first rendering and after every update.

The second argument is an array that specifies the dependencies of the first argument (the side effect function). The side-effect function is executed only if the variables in the array change.

As for useEffect Hook, you can refer to the official document of React for more basic usage. UseEffect is relatively comprehensive in the document, so I am not too cumbersome.

Implement useUpdateEffect

There is a componentDidUpdate lifecycle in the Class component. It will be called immediately after the update and will not be executed for the first rendering.

UseEffect can be used as an additional wrapper for componentDidUpdate in Function Component:

First we can implement a Hook using useRef to determine if it is the first time to render:

import { useRef } from 'react';

export function useFirstMountState() :boolean {
  const isFirst = useRef(true);

  if (isFirst.current) {
    isFirst.current = false;

    return true;
  }

  return isFirst.current;
}
Copy the code

If you’re not already familiar with useRef, I’ll take you through its mechanics later. The only thing you need to know here is useFirstMountState. This hook will return true the first time the component renders and false the rest of the time.

The next step is to use useFirstMountState to determine if the page is being rendered for the first time. You just need to check if it’s the first update in Effect:

import { useEffect } from 'react';
import { useFirstMountState } from './useFirstMountState';

const useUpdateEffect: typeof useEffect = (effect, deps) = > {
  const isFirstMount = useFirstMountState();

  useEffect(() = > {
    if(! isFirstMount) {return effect();
    }
  }, deps);
};

export default useUpdateEffect;
Copy the code

useContext

Context provides a way to share such values between components without explicitly passing props layer by layer through the component tree.

Those familiar with React Context Api and Vue provide/ Inject Api will be familiar with the function of this hook.

Consider this scenario:

On the root-level component we need to pass down a username property to each child component for use.

At this point, it would be a nightmare to pass layers through using the props method. In addition, if component G needs to use username but component B and component E don’t, it is necessary to explicitly declare username inside component B and component E.

React provides the Context Api to deal with situations like this.

You can create a context object with React. CreateContext, and pass the username in the component through the value property of context. Provider. Use useContext(Context) in the Function Component to get the corresponding value.

UseContext (MyContext) just lets you read the value of the context and subscribe to changes in the context. You still need to use < myContext. Provider> in the upper component tree to provide context for the lower component.

Context && useContext can be found here. The API is fully explained on the official website.

useReducer

React Hook provides an additional useReducer for state management.

UseReducer usage

const [state, dispatch] = useReducer(reducer, initialArg, init);

The useReducer accepts a Reducer function, an initial value initialArg, and an optional lazy init function.

It receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method.

Let’s go through a simple counter example to understand its basic usage:

import { useReducer } from 'react';

interface IState {
  count: number;
}

interface IAction {
  type: 'add' | 'subtract';
  payload: number;
}

const initialState: IState = { count: 0 };

const reducer = (state: IState, action: IAction) = > {
  switch (action.type) {
    case 'add':
      return { count: state.count + action.payload };
    case 'subtract':
      return { count: state.count - action.payload };
    default:
      throw new Error('Illegal operation in reducer.'); }};function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      <h1>Hello , My name is 19Qingfeng.</h1>
      <p>Counter: {state.count}</p>
      <p>
        <button onClick={()= > dispatch({ type: 'add', payload: 1 })}>
          add 1!
        </button>
      </p>
      <p>
        <button onClick={()= > dispatch({ type: 'subtract', payload: 1 })}>
          subtract 1!
        </button>
      </p>
    </>
  );
}

export default Counter;
Copy the code

Here we create a simple Counter Counter component that internally manages the state of the couter via the useReducer.

You can try out this little counter example here.

useState & useReducer

The counter example above can also be implemented with setState. Most students will have a question when writing component:

“When to use useState and when to use useReducer, what advantages/disadvantages does useReducer have over useState?”

In fact, useState can fully meet the role of daily development in most cases. After all, if we use action -> Reducer -> store to manage the state for a simple operation, it is a little overkill.

Most articles will tell you that useReducer is suitable for complex state logic.

Yes, we also use reducer function to distribute state updates according to different actions when there are multiple complex state management.

However, if there are many operation states in a certain state, each operation has a lot of logic. For such a complex state, using useState to have a separate function management may be clearer than using reducer to have multiple different actions in a single function.

As for “when to use useState and when to use useReducer”, in my opinion, the use of these two methods is more like a trade-off. In short, try to use the method that is comfortable for you and easier for you and your colleagues to understand.

Deeply updated components for performance optimization

In the official documentation of useReducer, there is a sentence like this:

Also, using useReducer can optimize performance for components that trigger deep updates because you can pass dispatches to child components instead of callbacks

In some cases we usually pass functions as props to the Child Component, so that every time the parent re-render, even if we don’t modify the function as props, the child component will re-render.

Let’s take a look at this example:

/ / the parent component
import { useState } from 'react';
import ChildComponent from './Child';

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

  const callback = () = > {
    return 10;
  };

  return (
    <div>
      <h3>Hello This is Parent Component!</h3>
      <p>ParentCount: {count}</p>
      <button onClick={()= > setCount(count + 1)}>Click Me!</button>
      <br />
      <ChildComponent callback={callback} />
    </div>
  );
}

export default ParentComponent;
Copy the code
/ / child component
import React, { FC, useEffect } from 'react';

interfaceProps { callback? :() = > number;
}

const ChildComponent: FC<Props> = ({ callback }) = > {
  useEffect(() = > {
    alert('child re-render');
  }, [callback]);

  return (
    <>
      <h1>Hello This is Child Component</h1>
      <p>{callback && callback()}</p>
    </>
  );
};

export default ChildComponent;
Copy the code

Here we pass a callback function to the child component as props in the parent component to see what happens when we click the button on the page:

Every time the parent component’s button is clicked, an effect is executed in the child component.

At this point, the callback we pass to the child component doesn’t change anything, and we naturally expect the Effect in the child component not to execute.

The mechanism for this is that React re-executes component functions each time it renders, and regenerates a callback function when the parent component is re-executed. Because React uses object. is internally, React considers that the props of the child component have changed.

You can check out the CodeSanBox example here

The action returned in useReduce is a function, but one of the advantages of useReducer is that dispatch does not reassign memory locations with re-render, For example, if we pass Dispatch as props to the Child Component, Effect in the child component will not be executed.

If you are interested, you can try it in private. Of course, the same effect can be achieved using useCallback, including the parent function in the Demo above, but this means that we have a lot of callback tied to useCallback. That may not be a good thing.

useCallback

Let’s take a look at useCallback, which is best used for performance optimizations in React.

As usual, let’s start with the basics:

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

UseCallback accepts two arguments:

  • The first argument is a function that will only be regenerated if the corresponding dependency changes, or the function is “memorized.”

  • The second argument is an array representing the dependencies on which the first argument depends, and the function of the first argument is “erased” and regenerated only if one of the items in the array changes.

Most people who know React will be curious about how to use this Hook. Let’s recall the example described in the useReducer section.

We passed a callback function in the parent component as props to the child component. We didn’t change the callback in each render, but each time the parent re-render, React will still assume that callback changes resulting in redundant child re-render.

If you forget this example, go to useReducer and review it again.

At this point, using useCallback is a good way to solve this example.

Let’s modify the code in the parent component above a bit:

import { useCallback, useState } from 'react';
import ChildComponent from './Child';

function ParentComponent() {
  const [count, setCount] = useState(0);
    
  // Here we use useCallback for the package
  const callback = useCallback(() = > {
    return 10; } []);return (
    <div>
      <h3>Hello This is Parent Component!</h3>
      <p>ParentCount: {count}</p>
      <button onClick={()= > setCount(count + 1)}>Click Me!</button>
      <br />
      <ChildComponent callback={callback} />
    </div>
  );
}

export default ParentComponent;
Copy the code

You can see that we use useCallback to wrap the callback function passed to the child component, while the second dependency argument passes an empty array.

Now let’s have a look at the display effect of the page:

If we click the button multiple times, the child component’s Effect will no longer execute.

You can check out CodeSanBox here.

useMemo

UseMemo role

If useCallback was provided to developers by the React team as a way to optimize functions, then useMemo can be seen as a way to “memorize” values and thus optimize performance.

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

UseMemo is also a Hook for performance optimization, which allows any type of value to be remembered compared to useCallback.

Again, it supports two arguments:

  • The first argument takes a function passed in, and the return value of the function call passed in is “remembered”. It is only when the dependency changes that the function passed in is re-evaluated for the new return.

  • The second argument is also an array that represents the dependencies corresponding to the first argument.

Let’s look at an example:

import React, { useState } from 'react';

const data = [
  { name: 'mathematics'.id: 0 },
  { name: 'Chinese'.id: 1 },
  { name: 'English'.id: 2 },
  { name: 'chemistry'.id: 3 },
  { name: 'biological'.id: 4},];function Demo() :React.ReactElement {
  const [count, setCount] = useState(0);
  const renderSubject = (() = > {
    console.log('Recalculate renderSubject ! ');
    return data.map((i) = > <li key={i.id}>{i.name}</li>); }) ();return (
    <div>
      <p>count: {count}</p>
      <button onClick={()= > setCount(count + 1)}>Click Me !</button>
      <br />
      <h1>Subjects:</h1>
      <ul>{renderSubject}</ul>
    </div>
  );
}

export default Demo;
Copy the code

RenderSubject is recalculated every time we click on the Button component re-render. .

React remember the value of the renderSubject and try again:

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

const data = [
  { name: 'mathematics'.id: 0 },
  { name: 'Chinese'.id: 1 },
  { name: 'English'.id: 2 },
  { name: 'chemistry'.id: 3 },
  { name: 'biological'.id: 4},];function Demo() :React.ReactElement {
  const [count, setCount] = useState(0);
  const renderSubject = useMemo(() = > {
    return (() = > {
      console.log('Recalculate renderSubject ! ');
      return data.map((i) = > <li key={i.id}>{i.name}</li>);
    })();
  }, []);

  return (
    <div>
      <p>count: {count}</p>
      <button onClick={()= > setCount(count + 1)}>Click Me !</button>
      <br />
      <h1>Subjects:</h1>
      <ul>{renderSubject}</ul>
    </div>
  );
}

export default Demo;
Copy the code

When we click the button on the page and the count changes to the re-render page, Because we use useMemo to pass in a function that returns data.map((I) =>

  • {i.name}
  • ) and the second argument is an empty array.

    No matter how re-render the page, the value returned in useMemo will not be recalculated as long as the dependency does not change.

    The sample code in this article is designed to show the Hook function. Here you can understand the meaning of the expression.

    One thing I want to say about performance tuning

    UseCallback and useMemo are two hooks that React provides to developers as means of performance optimization.

    But most of the time, you don’t need to worry about optimizing unnecessary re-renders. React is very fast, and I can think of a lot of things you can do with your time that are much better than doing these kinds of optimizations.

    In the case of useCallback and useMemo, I personally believe that improper use of these hooks not only makes the code more complex, but also leads to performance degradation by calling the built-in hooks to prevent dependencies and Memoized values from being garbage collected.

    If there are cases where you can get the necessary performance gains from using these hooks, such as particularly complex interactive charts and animations, then these costs are worth bearing, but it’s best to measure them before using them.

    The official documentation states that there is no need to worry about creating functions that will cause performance problems. The examples we provide above are just to show you how they can be used, and their use is not recommended in real life scenarios.

    For more information about useCallback and useMemo myths, check out this article on useCallback/useMemo myths

    useRef

    UseRef Hook has two main functions:

    • A bond that guarantees a unique value between multiple renders.

    UseRef keeps a unique reference to the returned value in all render. Because all assignments and values to ref are the final state, there is no different isolation in different render.

    We have already shown an example of this in useEffect Hook at the beginning, to determine if this is due to a page update rather than the first rendering:

    import { useRef } from 'react';
    export function useFirstMountState() :boolean {
      const isFirst = useRef(true);
      if (isFirst.current) {
        isFirst.current = false;
        return true;
      }
      return isFirst.current;
    }
    Copy the code
    • Get the Dom element. In the Function Component we can get the corresponding Dom element using the useRef.

    I have explained the functions and uses of useRef in detail in this article. You can check it out by clicking the link.

    useImperativeHandle

    UseImperativeHandle is a Hook that many students may not use very much in daily life, but in some cases, it can help us achieve some unintended effects.

    useImperativeHandle(ref, createHandle, [deps])
    Copy the code
    • Ref represents the ref object that needs to be assigned.

    • CreateHandle returns the value of ref. Current.

    • Deps depends on arrays, and createHandle is re-executed when the dependency changes.

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

    Let’s look at an example:

    import React, {
      ForwardRefRenderFunction,
      useImperativeHandle,
      forwardRef,
      useRef,
    } from 'react';
    
    interface Props {
      name: string;
    }
    
    const Input: ForwardRefRenderFunction<{ focus: () = > void }, Props> = (props, ref) = > {
      const inputRef = useRef<HTMLInputElement>(null);
      useImperativeHandle(
        ref,
        () = > ({
          focus: () = >inputRef.current? .focus(), }), [] );return (
        <div>
          <input ref={inputRef}></input>
        </div>
      );
    };
    
    const ExportInput = forwardRef(Input);
    
    export default ExportInput;
    Copy the code

    In the example above, we use the useImperativeHandle function to include the ref object passed in from the outside.

    We specify that only one focus method is exposed when the external obtains the component instance through ref.

    This corresponds to the useImperativeHandle that lets you customize the instance values exposed to the parent component when using a ref.

    Of course, there might be a situation like this in everyday React development. We want to call the child’s methods in the parent component. React doesn’t officially recommend this declarative approach, but sometimes we have to.

    Let’s rewrite the above example a bit:

    import React, {
      ForwardRefRenderFunction,
      useImperativeHandle,
      forwardRef,
      useRef,
    } from 'react';
    
    interface Props {
      name: string;
    }
    
    export interface InputExportMethod {
      focus: () = > void;
      domeSomeThing: () = > void;
    }
    
    const Input: ForwardRefRenderFunction<InputExportMethod, Props> = (props, ref) = > {
      const inputRef = useRef<HTMLInputElement>(null);
    
      // Subcomponent methods
      const domeSomeThing = () = > {
        // dosomething
        console.log('do smething');
      };
    
      useImperativeHandle(
        ref,
        () = > ({
          focus: () = >inputRef.current? .focus(),domeSomeThing: () = > domeSomeThing(),
        }),
        []
      );
    
      return (
        <div>
          <input ref={inputRef}></input>
        </div>
      );
    };
    
    const ExportInput = forwardRef(Input);
    
    export default ExportInput;
    
    Copy the code

    At this point I can call the method exposed by useImperativeHandle from the parent component using Input via ref to the child component:

    import React, { useEffect, useRef } from 'react';
    import Input, { InputExportMethod } from './index';
    
    const Parent: React.FC = () = > {
      const inputRef = useRef<InputExportMethod>(null);
    
      useEffect(() = > {
        if (inputRef.current) {
          console.log(inputRef.current.domeSomeThing()); }} []);return <Input ref={inputRef} name="19Qingfeng"></Input>;
    };
    
    export default Parent;
    Copy the code

    When we open the page, the console successfully prints:

    useLayoutEffect

    UseLayoutEffect is used in exactly the same way as useEffect. UseLayoutEffect differs in that 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.

    In general, if you want to reduce useEffect “page jitter” for layouts computed by JS, you can consider using useLayoutEffect instead.

    Take a look at this code:

    import React, { useEffect, useRef, useState } from 'react';
    
    function Demo() {
      const ref = useRef<HTMLDivElement>(null);
      const [style, setStyle] = useState<React.CSSProperties>({
        position: 'absolute'.top: '200px'.background: 'blue'}); useEffect(() = > {
        for (let i = 0; i < 1000; i++) {
          console.log(i);
        }
        if (ref.current) {
          const { width, height, top, left } = ref.current.getBoundingClientRect();
          setStyle({
            width: width + 'px'.height: height + 'px'.top: top + 'px'.left: left + 'px'}); }} []);return (
        <div>
          <div
            ref={ref}
            style={{ width: '200px', height: '200px', margin: '30px'}} >
            Hello
          </div>
          <div style={{ . style.position: 'absolute' }}> This is 19Qingfeng.</div>
        </div>
      );
    }
    
    export default Demo;
    Copy the code

    Here we use useEffect to recalculate This is 19Qingfeng after page refresh. The location of this div.

    Most of the time you probably won’t feel anything when you refresh the browser, so let’s try slowing down the browser CPU speed and try again.

    Here I have reduced the CPU speed under Chrome Performance, so let’s look at the effect again:

    In the GIF above, you can clearly see that the page initially had a blue div bouncing, and you can clearly see that the page flickers with useEffect through the performance analysis:

    If we call useEffect useLayoutEffect instead, the content in the page useLayoutEffect will be updated synchronously before rendering the page. If you are interested, you can verify this privately for yourself.

    React hooks are all javascripts. If you’ve ever encountered an incorrect location of a page element in a Hook, this useEffect will help you out.

    Of course, there are some special cases of useLayoutEffect:

    Sometimes you may need to use another case using useLayoutEffect instead of useEffect, if you want to update the value (like ref), at this point you need to make sure it is up to date by any other code before running it. Such as:

    const ref = React.useRef()
    React.useEffect(() => {
      ref.current = 'some value'
    })
    
    // then, later in another hook or something
    React.useLayoutEffect(() => {
      console.log(ref.current) // <-- this logs an old value because this runs first!
    })
    Copy the code

    In this particular case, using useLayoutEffect is a very good solution.

    useDebugValue

    useDebugValue(value , fn)
    Copy the code

    UseDebugValue can be used to display the tag of a custom hook in the React developer tool. It takes two arguments:

    • Value is the variable we will focus on and represents the hook flag displayed in DevTools.
    • Fn indicates how to format the variable value, which is called only when the Hook is checked. It takes a debug value as an argument and returns a formatted display value.

    When we customize some hooks, we can use the useDebugValue and React DevTools to quickly locate our user-defined hooks.

    import { useDebugValue, useState } from 'react';
    
    function useName() {
      const [state] = useState('19Qingfeng');
      useDebugValue('19Qingfeng');
      return state;
    }
    
    function App() {
      const name = useName();
      return <div>{name}</div>;
    }
    Copy the code

    In this code, I use useDebug to define a 19Qingfeng flag. Now let’s check React DevTools:

    Additional notes are needed:

    • UseDebugValue should be used in custom hooks; it is not valid if used directly within components.
    • Most of the time you don’t need to use this Hook, unless you explicitly flag the Hook when writing some common library hooks.

    2. If you use useDebugValue, it is better to set the second parameter to format the first parameter during each check.

    At the end

    Thank every friend who can read to the end, I hope the content of the article can help you.

    After Webpack, I will implement these 9 hooks from scratch in React. If you are interested, please follow my React column.