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 data
themes
(withdefault
andred
Two themes) - Create one for the topic data
content
object - export
themes
andcontext
The 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 good
ThemesContext
Object, and the button component - To create a
theme
State. - use
ThemesContext.Provider
The component packageAdd 1Button, and willtheme
State toThemesContext.Provider
thevalue
data - Let’s create another oneChange the theme of the add 1 buttonButton (will change after clicking
ThemesContext.Provider
thevalue 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 created
ThemesContext
object - use
useContext
callThemesContext
The values in the - I’m going to give it to
button
// 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:
- create
countState
- create
countReducer
.countReducer
Accept 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:
- reference
Button
Component and getButton
componentref
- Delay one second after rendering
Button
The component’sexecClick
methods
//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:
- use
forwardRef
Method gets the second parameter to the function componentref
- use
useImperativeHandle
To exposeexecClick
methods
// 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 ~