preface

This article follows this article: Mastering React Hook use and Rendering Optimization (Part 1)

If you’re not familiar with React Hooks or even react debugging, I suggest you read the previous article first.

This article will cover the following hooks

1, useContext 2, useReducer 3, useImperativeHandle 4, custom hook 5, useDebugValue

To prepare

If your page is in the Demo folder, you need to create the following files in the Demo folder:

demo

  • Index.js Page code
  • A Button component in a button.js page
  • Themescontext.js Context object file

In addition to preparing the above code files, you also need to install the React Developer Tools for Chrome.

The ts code in this article will explain the meaning of the code in detail, and all children who do not know TS will not matter.

useContext

Receives a context object (the return value of React.createcontext) and returns the current value of the context. The value of the current context is determined by the value prop of < myContext. Provider> nearest to the current component in the upper-layer components. Components that call useContext are rerendered when the context value changes

A child component can use useContext to receive a variable from its parent (parent or parent’s parent…). Components. Components that use useContext are rerendered when the parent variable is changed (not otherwise).

What’s the use of 🤔? If you think about it, how do you achieve a topic switch without this feature? If we don’t use the context object, we need to pass the theme style layer by layer to each component. 🤣.

UseContext must be used with React.createcontext. To useContext (useContext), you must first create a context (createContext)

CreateContext and useContext are defined and called.

The TypeScript type definition for the createContext function:

/ /... type Provider<T> = ProviderExoticComponent<ProviderProps<T>>; type Consumer<T> = ExoticComponent<ConsumerProps<T>>; interface Context<T> { Provider: Provider<T>; Consumer: Consumer<T>; displayName? : string; } function createContext<T>(// Why is defaultValue mandatory? // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24509#issuecomment-382213106 defaultValue: T, calculateChangedBits?: (prev: T, next: T) => number ): Context<T>;Copy the code

Definition:

The definition states that when the createContext function is called, it accepts two parameters. The first parameter defaultValue is mandatory and the second parameter calculateChangedBits is optional. The createContext function returns a Context object with static properties Provider, Consumer, and displayName. Provider and Consumer are the React components.

Note that Consumer is not usually used in function components. We can just use useContext instead. (The methods used are shown below)

In the above code, T is the description type of TS. In TS, the type description of the context can be passed into the createContext call:

interface ColorContext { color:'red'|'green' }
const colorContext = createContext<ColorContext>({ color:"red" })
Copy the code

The useContext function TypeScript type definition:

function useContext<T>(context: Context<T>): T;  
Copy the code

When useContext is called, you simply pass in the context object that was originally created using createContext. When the createContext is created, the function returns a value (T).

😎 Next let’s achieve a button theme switch function!

🌰 show code

First we should create a Content object.

In this file we need to do:

  • Create a topic datathemes(withdefaultandredTwo themes)
  • Create one for the topic datacontentobject
  • exportthemesandcontextThe standby
// ThemesContext.js
import React, { createContext } from 'react'; 
const themes = {
    default: {
        key: "default",
        color: "#000",
    },
    red: {
        key: "red",
        color: "red",
        backgroundColor: "transparent",
        border: "2px solid red",
        borderRadius: 8
    }
}

const ThemesContext = createContext(themes.default);
export { themes }
export default ThemesContext;
Copy the code

Let’s write a page code

In this file we need to do:

  • Reference topic and create goodThemesContextObject, and the button component
  • To create athemeState.
  • useThemesContext.ProviderThe component packageAdd 1Button, and willthemeState toThemesContext.Providerthevaluedata
  • Let’s create another oneChange the theme of the add 1 buttonButton (will change after clickingThemesContext.Providerthevalue prop)
// index.js import React, { useCallback, useState } from 'react'; import ThemesContext, Themes} from "./ThemesContext" import Button from "./Button" const Index = () => {const [count, setCount] = useState(0); const [theme, setTheme] = useState(themes.default); const onClick = useCallback(() => { setCount(prevState => prevState + 1) }, []) const changeThemes = useCallback(() => { setTheme(prevState => prevState.key === "red" ? themes.default : themes.red) }, []) return <div style={{ padding: 12}}> <p>count:{count}</p> < themescontext. Provider value={theme}> </p> </ themescontext. Provider> <Button onClick={changeThemes}> </Button> < div>}; export default IndexCopy the code

And our Button component code

In this file we need to do:

  • Reference createdThemesContextobject
  • useuseContextcallThemesContextThe values in the
  • I’m going to give it tobutton
// Button.js import React, { useContext, memo } from 'react'; Import ThemesContext from "./ThemesContext" const Button = (props) => {const theme = useContext(ThemesContext); const btnStyle = { ... theme, padding: "6px 12px", marginLeft: 8 } return <button onClick={props.onClick} style={btnStyle}> {props.children} </button> } export default memo(Button)Copy the code

✨ The execution effect is as follows:

This enables you to switch themes.

Ps: Components wrapped with themesContext. Provider are not rerendered when the value of themesContext. Provider changes, but when the child component calls useContext.

🤔 How do you use context in a class component? (You cannot use hooks in class components)

We have another way to call it, which we just need to modify in the Button component:

//Button.js import React, { useContext, memo } from 'react'; Import ThemesContext from "./ThemesContext" const Button = (props) => {// not using useContext return < themesContext.consumer > {(theme) => { const btnStyle = { ... theme, padding: "6px 12px", marginLeft: 8 } return <button onClick={props.onClick} style={btnStyle}> {props.children} </button> }} </ThemesContext.Consumer> } export default memo(Button)Copy the code

The execution is exactly the same as using useContext, except that the themesContext. Consumer component is used to invoke the data.

ThemesContext displayName use

Above has completed the context of the creation and references, the ThemesContext. Use 🤔 displayName have?

React DevTools uses this string to determine what context is to display.

Let’s see. Do not use the ThemesContext displayName this method the React DevTools ThemesContext. Shown in the name of the Provider component

As you can see, the themesContext. Provider component is displayed with the default name content. Provider, which makes debugging cumbersome if we use multiple xxx.Provider. At least we can’t immediately see which content we’re looking for.

All we need to do is change themescontext.js

/ /... const ThemesContext = React.createContext(themes.default); / / add name ThemesContext here. DisplayName = "ThemesContext" export {themes} export default ThemesContext;Copy the code

✨ The execution effect is as follows:

So we can use our own name to display!

useReducer

An alternative to useState. It receives a Reducer of the form (state, action) => newState and returns the current state and its corresponding dispatch method. (If you’re familiar with Redux, you already know how it works.)

The use of this hook is the same as that of Redux. If you can Redux, the learning cost is almost zero.

The TypeScript type definition for the useReducer function:

/ /... type ReducerWithoutAction<S> = (prevState: S) => S; type Reducer<S, A> = (prevState: S, action: A) => S; type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never; type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never; type ReducerStateWithoutAction<R extends ReducerWithoutAction<any>> = R extends ReducerWithoutAction<infer S> ? S : never; function useReducer<R extends ReducerWithoutAction<any>, I>( reducer: R, initializerArg: I, initializer: (arg: I) => ReducerStateWithoutAction<R> ): [ReducerStateWithoutAction<R>, DispatchWithoutAction]; function useReducer<R extends ReducerWithoutAction<any>>( reducer: R, initializerArg: ReducerStateWithoutAction<R>, initializer? : undefined ): [ReducerStateWithoutAction<R>, DispatchWithoutAction]; function useReducer<R extends Reducer<any, any>, I>( reducer: R, initializerArg: I & ReducerState<R>, initializer: (arg: I & ReducerState<R>) => ReducerState<R> ): [ReducerState<R>, Dispatch<ReducerAction<R>>]; function useReducer<R extends Reducer<any, any>, I>( reducer: R, initializerArg: I, initializer: (arg: I) => ReducerState<R> ): [ReducerState<R>, Dispatch<ReducerAction<R>>];Copy the code

Definition:

As can be seen from the code described above, the useReducer method accepts three parameters — Reducer, initializerArg, and initializer. The initializer is optional. The reducer function is the same as Redux’s reducer function. InitializerArg is a function that takes an initial value (initializerArg), and the reducer function takes an initial value (initializerArg). The initial value can be processed (this will allow the logic used to calculate state to be taken outside the Reducer, which makes it easier to do the resetting of state action in the future)

The return value is similar to that of useState. The first element is the state value and the second element is Dispatch. After the execution, the reducer is called and the state value is changed

In some scenarios, useReducer may be more applicable than useState, 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 allows performance optimization for components that trigger deep updates because you can pass dispatches to child components instead of callbacks

😎 Next let’s implement a counter with increase, decrease, reset function!

🌰 show code

In this file we need to do:

  • createcountState
  • createcountReducer.countReducerAccept three types (increase, decrease, and reset)
// index.js import React, { useCallback, useReducer } from 'react'; Import Button from "./Button" // used to process initial state const initFn = (initState) => {return {... InitState}} const Index = () => { Const countReducer = (prevState, @prevState, @prevState) const countReducer = (prevState, @prevState, @prevState) action) => { switch (action.type) { case "increment": return { count: prevState.count + 1 } case "decrement": return { count: prevState.count - 1 } case "reset": // The third parameter of useReducer is used to initialize the return initFn(action.payload) default: Return prevState}} //@initFn Const [countState, countDispatch] = useReducer(countReducer, initState, initFn); const add = useCallback(() => { countDispatch({ type: "increment" }) }, []) const decrement = useCallback(() => { countDispatch({ type: "decrement" }) }, []) const reset = useCallback(() => { countDispatch({ type: "reset", payload: initState }) }, []) return <> <p>count:{countstate. count}</p> <Button onClick={add}> + 1</Button> <Button onClick={decrement}> + 1</Button> <Button onClick={reset} > </Button> </>}; export default IndexCopy the code

In this file we need to do:

  • There’s nothing special to do, just a button component
// Button.js
import React, { useContext, memo } from 'react'; 

const Button = (props) => { 
    const btnStyle = {
        padding: "6px 12px",
        marginLeft: 8
    }

    return <button onClick={props.onClick} style={btnStyle}>
        {props.children}
    </button>
}

export default memo(Button)
Copy the code

✨ The execution effect is as follows:

This is a very powerful feature. You can use it more when you have the chance

useImperativeHandle

UseImperativeHandle lets you customize the instance value exposed to the parent component when using ref. UseImperativeHandle should be used with the forwardRef:

The API is simple to use. If you write a component and call some of its methods through ref when calling the component, you can use useImperativeHandle to define which methods can be called.

UseImperativeHandle TypeScript type definition:

type DependencyList = ReadonlyArray<any>; function useImperativeHandle<T, R extends T>(ref: Ref<T>|undefined, init: () => R, deps? : DependencyList): void;Copy the code

As we can see from the definition, useImperativeHandle takes three parameters. The first is ref(a parameter that exists when a component wraps with forwardRef) and the second is init, a function that returns an object. The third method is DependencyList, which updates the methods provided by useImperativeHandle

We will also use useRef and forwardRef when using this hook, because these three are always together.

UseRef is a class component called createRef, which returns an object with a current property.

Next, we implement the above code by actively calling the methods provided by the Button component in the parent component

🌰 show code

In this file we need to do:

  • referenceButtonComponent and getButtoncomponentref
  • Delay one second after renderingButtonThe component’sexecClickmethods
//index.js import React, { useCallback, useEffect } from 'react'; import Button from "./Button" const Index = () => { const [count, setCount] = React.useState(0); const buttonRef = React.useRef(null) const onClickFn = useCallback(() => { setCount(count + 1) }, [to count]) useEffect (() = > {/ / after the rendering time delay a second call Button component execClick method setTimeout (() = > {buttonRef. Current. ExecClick (); }}, 1000), []); Return <div> <p>count:{count}</p> <Button ref={buttonRef} onClick={onClickFn}> + 1</Button> </div>}; return <div> <p>count:{count}</p> <Button ref={buttonRef} onClick={onClickFn}> + 1</Button> </div>}; export default IndexCopy the code

In this file we need to do:

  • useforwardRefMethod gets the second parameter to the function componentref
  • useuseImperativeHandleTo exposeexecClickmethods
// button. js import React, {useImperativeHandle, forwardRef} from 'React '; Const Button = (props, ref) => {useImperativeHandle(ref, () => ({execClick: props.onClick })) return <> <button onClick={props.onClick} style={{ padding: "6px 12px" }}> {props.children} </button> </> } export default forwardRef(Button)Copy the code

✨ The execution effect is as follows:

Let’s print buttonref.current and see

You can see that this is actually the method we are exposing using the useImperativeHandle function.

This hook is actually very easy to understand ~, if you don’t understand it, just try it yourself!

Customize the hook

A custom Hook is a function whose name begins with “use”. Other hooks can be called inside the function

At this point, you have learned the common hooks.

Let’s implement a simple custom hook. After reading this simple hook, you can continue to look at the case provided by the official document: click direct

When setting state, we need to determine if the component is still in the rendered state under certain circumstances, such as an operation that requests data. If the component has already been uninstalled, we will receive an error if we continue to set state.

In the function component we need to obtain whether the component has been unloaded:

Use useEffect hook, pass in an empty array of dependencies, and then return a function. The returned function is executed when the component is unloaded. At this point we can use the returned function to record the isGone state

Const [isGone, setIsGone] = useState(true); // If the component is unmounted, it is initialized to rue // When the component is rendered, it is false // If the component is unmounted, it is true const [isGone, setIsGone] = useState(true); useEffect(() => { setIsGone(false) return () => { setIsGone(true) }; } []);Copy the code

This logic can be used by many components. At this point we can use custom hooks.

🌰 show code

First we create a custom hook file:

// useIsGone.js import { useState, useEffect, useDebugValue } from 'react'; function useIsGone() { const [isGone, setIsGone] = useState(true); useEffect(() => { setIsGone(false) return () => { setIsGone(true) }; } []); return isGone } export default useIsGoneCopy the code

As you can see, useIsGone returns the isGone state.

Let’s call it

import React from 'react'; import useIsGone from "./useIsGone" const Index = () => { const isGone = useIsGone(); Console. log('isGone: ', isGone) return <div> custom hook </div>}; export default IndexCopy the code

✨ The execution effect is as follows:

You can see that isGone has changed from false to true.

useDebugValue

UseDebugValue can be used to display a label for custom hooks in the React developer tools.

The API is simple to use. Give our custom hooks a name to make it easier to find them when debugging.

UseImperativeHandle TypeScript type definition:

function useDebugValue<T>(value: T, format? : (value: T) => any): void;Copy the code

UseDebugValue takes two parameters: value and format (optional).

🌰 show code

Here we only modify the custom component code. The rest of the code is the same as in the previous section.

import { useState, useEffect, useDebugValue } from 'react'; function useIsGone() { const [isGone, setIsGone] = useState(true); // useDebugValue can be used to display a custom hook tag in the React developer tool. // useDebugValue("xm isGone"); useEffect(() => { setIsGone(false) return () => { setIsGone(true) }; } []); return isGone } export default useIsGoneCopy the code

✨ The execution effect is as follows:

🤔 What’s the use of the second parameter?

The second parameter is also useful. The second argument is used to format the name when the debugger displays it. We modify the code as follows:

import { useState, useEffect, useDebugValue } from 'react'; function useIsGone() { const [isGone, setIsGone] = useState(true); // useDebugValue can be used to display a custom hook tag in the React developer tool. // useDebugValue("xm isGone"); (name)=>{ return `${name}_${new Date().getMinutes()}:${new Date().getSeconds()}` }); useEffect(() => { setIsGone(false) return () => { setIsGone(true) }; } []); return isGone } export default useIsGoneCopy the code

✨ The execution effect is as follows:

😂 See? This time will be up to date every time the name is displayed.

React Hook article here to stop!

Like the words of the point like ~