This article is used to take notes on learning and using React Hook.
Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class. A Hook is inside a classDon’tIt works. But you can use them instead of classes.
What can a Hook do?
- Used in function components
state
(useState) - Extract component state logic (custom hook)
Initialize the
Use create-react-app to initialize projects that support TypeScript syntax.
npx create-react-app base-demo --typescript
Copy the code
useState
Before hooks were introduced, function components (stateless components) were commonly used for UI rendering, represented by data passed through props. If a component wants to have its own state data, it can only do so through the class component. UseState provides methods for preserving its own state in function components. Usage:
import React, {useState} from 'react';
const Counter: React.FC = () = >{
const [counter, setCounter] = useState(0);
return <div>
<span>{counter}</span><button onClick={()= >{setCounter(counter+1)}}>+1</button>
</div>
}
export default Counter;
Copy the code
Counter is the variable defined, setCounter is the method used to change the value of counter.
useEffect
Effect Hook allows you to perform side effects in function components.
If you’re familiar with React class lifecycle functions, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount.
Effect that does not need to be cleaned
We want to do the same when the component loads and updates. Conceptually, we want it every timeApply colours to a drawingThen it executes — but the React class component doesn’t provide such a method. Even if we extract a method, we still have to call it in two places.
import React, { useState, useEffect } from 'react';
const Counter: React.FC = () = >{
const [counter, setCounter] = useState(0);
const [flag, setFlag] = useState(true);
useEffect(() = >{
document.title = `You clicked ${counter} times`;
console.log("useEffect...")});return <div>
<span>{counter}</span>
<button onClick={()= >{setCounter(counter+1)}} style={{marginLeft: "20px"}}>+1</button>
<button onClick={()= >{setFlag(! flag)}} style={{marginLeft: "20px"}}>{flag ? 'ON':'OFF'}</button>
</div>
}
export default Counter;
Copy the code
useEffect
The function is in everyApply colours to a drawingWhat does that mean? In the example, even though we are changing the value of flag, we defined ituseEffect
Functions are also executed. That’s not what we want,useEffect
The second argument in the function can be used to control the timing of execution.
Control useEffect execution
UseEffect actually takes two parameters:
- If the second parameter is not written by default,
useEffect(()=>{});
So useEffect function every pageApply colours to a drawingWill be executed. - If the second argument is an empty array
useEffect(()=>{}, []);
The useEffect function is executed independently of any other variables and only on the first page rendering. - If the useEffect function is executed in relation to a variable that requires some action when it changes, it can be passed into an array.
useEffect(()=>{}, [counter]);
useEffect(() = >{
document.title = `You clicked ${counter} times`;
console.log("useEffect...")
}, [counter]);
Copy the code
The useEffect function is executed only when the page’s counter data changes.
Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser update screen, making your application seem more responsive.
Effects that need to be cleared
There are also side effects that need to be removed. For example, events bound to document, setTimeout functions, etc. In this case, cleaning is very important to prevent memory leaks! Now let’s compare the implementation using Class and Hook. For example, in the Class component, we implement this:
import React from 'react';
class MouseTrigger extends React.Component{
constructor(props){
super(props);
this.state = {
position: {
x: 0.y: 0
}
}
}
handleMouseMove = (e) = >{
this.setState({
position: {
x: e.clientX,
y: e.clientY
}
})
}
// Add listening events
componentDidMount(){
document.addEventListener("click".this.handleMouseMove);
}
// Cancel the listening event
componentWillUnmount(){
document.removeEventListener("click".this.handleMouseMove);
}
render(){
return (
<div>
<p>Mouse position is ({this.state.position.x}, {this.state.position.y})</p>
</div>)}}export default MouseTrigger;
Copy the code
Implemented using the useEffect method, useEffect returns a function that clears “side effects”.
import React, { useState, useEffect} from "react";
const MouseTrigger: React.FC = () = >{
const [position, setPosition] = useState({x: 0.y:0})
const handleMouseMove = (e: MouseEvent) = >{
setPosition({
x: e.clientX,
y: e.clientY
})
}
useEffect(() = >{
document.addEventListener("click", handleMouseMove);
return () = >{
document.removeEventListener("click", handleMouseMove); }}, [])return <p>
Mouse position is ({position.x}, {position.y})
</p>
}
export default MouseTrigger;
Copy the code
useEffect(()=>{… Return ()=>{}}, []), the second argument is an empty array [], so that useEffect is executed only on the first page load; Returns a function that is executed when the component is unloaded to remove added “side effects”.
No contrast, no harm. Obviously, the function component defined by useEffect is more clear and concise, giving a refreshing feeling.
Customize the Hook
With custom hooks, component logic can be extracted into reusable functions. Before custom hooks were proposed, how did we implement them? There are two common ways, HOC and render props. Let’s take a look at the differences in usage
HOC
HOC is short for High Order Component, meaning “high-level Component.” A higher-order component is essentially a function that takes a component as an argument and returns a new component. In business, there is often a need to request data. Loadin animation is displayed during data request and corresponding data is displayed upon completion of the request. This logic can be stripped down as follows:
// FetchData.js
import React from 'react';
import axios from 'axios';
const FetchData = (Component, url) = >{
class WithFetchData extends React.Component{
constructor(props){
super(props);
this.state = {
data: [].isLoading: true}}componentDidMount(){
this.setState({
isLoading: true
});
axios.get(url).then(res= >{
if(res.status===200) {this.setState({
data: res.data,
isLoading: false})}})}render(){
const {isLoading, data} = this.state;
return <>
{ isLoading ? <p>data is loading</p>: <Component data={data}/>}
</>}}return WithFetchData;
}
export default FetchData;
Copy the code
FetchData is a higher-order component, essentially a function that takes a component and returns a new component. Use in required components:
// ShowData.js
import React from 'react';
import FetchData from './FetchData';
const ShowData = (props) = >{
return <>
{props.data.map((item, key)=><p key={key}>{item.name}, {item.price}</p>)}
</>
}
export default FetchData(ShowData, "/api/mockData.json");
Copy the code
HOC is essentially extracting the component logic into the parent component and wrapping it around it so that internal components can reuse externally defined data and logic.
render props
Render props is a technique that utilizes parent-child component pass-throughs. The component to be displayed is passed to the parent component via the Render function.
// RenderPropsDemo.js
import React from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
class RenderPropsDemo extends React.Component{
constructor(props){
super(props);
this.state = {
data: [].isLoading: true}}componentDidMount(){
this.setState({
isLoading: true
});
const {url} = this.props;
axios.get(url).then(res= >{
if(res.status===200) {this.setState({
data: res.data,
isLoading: false})}})}render(){
const {isLoading, data} = this.state;
const {render} = this.props;
return (
<>
{ isLoading ? <p>data is loading</p> : <>{render(data)}</>}
</>
)
}
}
RenderPropsDemo.prototypes = {
render: PropTypes.func.isRequired
}
export default RenderPropsDemo;
Copy the code
Use:
// ShowData.js
import React from 'react';
import RenderPropsDemo from './RenderPropsDemo';
const ShowData = () = >{
const render = (data) = >{
return data.map((item, key) = ><p key={key}>{item.name}, {item.price}</p>)}return <>
<RenderPropsDemo render={render} url="/api/mockData.json"/>
</>
}
export default ShowData;
Copy the code
Customize the Hook
With custom hooks, component logic can be extracted into reusable functions. In contrast to HOC and render props, custom hooks are pure function modes that don’t involve parent and child components. The logic is clearer.
//useFetchData.tsx
import { useState, useEffect } from 'react';
import axios from 'axios';
// Only the first request is initiated by default
const useFetchData = (url: string, deps: any[] = []) = >{
const [data, setData] = useState<any>([]);
const [loading, setLoading] = useState(false);
useEffect(() = >{
setLoading(true);
axios.get(url).then(res= >{
if(res.status===200){
setData(res.data);
setLoading(false);
}
})
}, deps);
/ / return
return [data, loading];
}
export default useFetchData;
Copy the code
Use:
//FetchData.tsx
import React from 'react';
import useFetchData from './useFetchData';
interface IShowDataResult{
name: string;
price: number;
}
type resultType = Array<IShowDataResult>;
const FetchData: React.FC = () = >{
// Run useFetchData to obtain the file
const [data, loading] = useFetchData("/api/mockData.json");
return <>
{
loading ? <p>data is loading</p> :
<>
{data.map((item:IShowDataResult, key: number)=><p key={key}>{item.name}</p>)}
</>} < / a >}export default FetchData;
Copy the code
The effect of the custom Hook implementation is similar to the Composition API idea in Vue 3.0. The idea is to strip the repetitive logic out of components and manage the code in a cleaner, more organized way.
Note:
- Custom hooks must start with “use”. 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.
- Two components using the same Hook do not share state. 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.
useRef
Gets the latest value
Take a look at the following example to compare the use of useState and useRef:
import {useState, useRef, useEffect} from 'react';
const Counter = () = >{
const [counter, setCounter] = useState(0);
const likeRef = useRef(0);
const handleAlert = () = >{
setTimeout(() = >{
alert(counter);
alert(likeRef.current);
}, 2000);
}
return (
<div>
<span>{counter}</span>
<button onClick={()= >{setCounter(counter+1); likeRef.current++; }} style={{marginLeft: '20px'}}>+1</button>
<button onClick={handleAlert} style={{marginLeft: '20px'}} >alert</button>
</div>)}export default Counter;
Copy the code
When we click the Alert button, we can see that the value of counter is not up to date, but likeref.current is.
Why is that?
This is because when we change the state React will re-render the component each time, each rendering will get a separate counter value and re-render a handleAlert function. Each handleAlert closure holds the render counter. That is, the last rendering has nothing to do with the next one. The next rendering does not affect the previous data. This is characteristic of state and props.
UseRef returns a mutable ref object. Ref keeps a unique reference in all render, so ref values get the final state instead of being isolated.
To obtain
UseRef can be used to retrieve and manipulate the DOM. An example: The input field is automatically focused when the page loads.
import {useState, useRef, useEffect} from 'react';
const Counter = () = >{
const inputRef = useRef(null);
useEffect(() = >{
if(inputRef && inputRef.current){ inputRef.current.focus(); }}, [])return (
<div>
<input type="text" ref={inputRef}/>
</div>)}export default Counter;
Copy the code
useContext
The state defined globally is available in the child component. When these states are changed, the child components used are triggered to re-render.
React.createContext(themes.light);
Initialize the global state, default themes.light- < themecontext. Provider value={themes.dark}> Assign themes.dark
useContext
Child components use global state.
// initializes the global state in app.js
import './App.css';
import React, {useState} from 'react';
import Counter from "./Counter";
const themes = {
light: {
foreground: "# 000000".background: "#eeeeee"
},
dark: {
foreground: "#ffffff".background: "# 222222"}}const ThemeContext = React.createContext(themes.light);
// Needs to be exported for use by child components.
export {ThemeContext};
function App() {
const [curTheme, setCurTheme] = useState(true);
const changeTheme = () = >{ setCurTheme(! curTheme); }return (
<ThemeContext.Provider value={curTheme ? themes.light: themes.dark} >
<Counter/>
<button onClick={changeTheme}>change</button>
</ThemeContext.Provider>
);
}
export default App;
Copy the code
Use in child components:
import {useState, useRef, useEffect, useContext} from 'react';
import { ThemeContext } from './App';
const Counter = () = >{
// Some code is omitted
const theme = useContext(ThemeContext);
const themeStyle = {
background: theme.background,
color: theme.foreground
}
return (
<div>
<input type="text" style={themeStyle}/>
</div>)}export default Counter;
Copy the code
UseMemo and useCallback
background
For a series of optimization stories triggered by a problem, see the following example:
class Foo extends React.Component{
render(){
console.log("render...")
return (
<p>{this.props.count}</p>)}}class App extends React.Component{
constructor(props){
super(props);
this.state = {
count: 0.double: 1
}
}
changeDouble = () = >{
this.setState({
double: this.state.double * 2})}render(){
return (
<>
<Foo count={this.state.count}/>
<p>count: {this.state.count}, double: {this.state.double}</p>
<button onClick={this.changeDouble}>Double</button>
</>)}}Copy the code
Normally we want the child Foo to be rerendered only if the parent count value changes, but in practice every time the parent renders itself, the child is also rendered. When we modify the value of double, the child component is also rerender. How do you solve this problem? There are the following methods:
- Solution a:
shouldComponentUpdate(nextProps, nextState)
Redefining whether an update is required in the lifecycle function - Plan 2: Use
PureComponent
Component to solve
ShouldComponentUpdate (nextProps, nextState) Life cycle function returns a Boolean, true by default, meaning that the component is rerendered each time. In this function, we can compare the value of the nextProps to be modified against the value of the current props. If it is the same, the component will not be re-rendered. Otherwise, return true and the component needs to be re-rendered.
class Foo extends React.Component{
shouldComponentUpdate(nextProps, nextState){
if(nextProps.count === this.props.count){
return false;
}
return true; }}Copy the code
Solution 2: Use PureComponent.
class Foo extends React.PureComponent{
render(){
console.log("render...")
return (
<p>{this.props.count}</p>)}}Copy the code
Inside the PureComponent is a superficial comparison of nextProps and this.props in the shouldComponentUpdate lifecycle function. Note that since this is a shallow comparison, there is a problem if we pass a callback function to a child component. Look at the following example:
class App extends React.Component{
render(){
return (
<>
<Foo count={this.state.count} cb={()= >{}} / ><button onClick={this.changeDouble}>Double</button>
</>)}}Copy the code
Every time the parent APP modifies the value of double, it causes its own render function to be executed. Cb ={()=>{}} is reexecuted when render is executed, so it is a different callback object each time. This results in repeated rendering of child components.
Solution: We need to make the callback function a variable in a class to avoid this problem.
class App extends React.Component{
cb = () = >{}
render(){
return (
<>
<Foo count={this.state.count} cb={this.cb}/>
</>)}}Copy the code
At this point, you can avoid the problem of useless repeated rendering of child components in the class component. But in functional components, these problems still exist.
We usually express components that have no state of their own as function components. Function components do not have shouldComponentUpdate and PureComponent. The corresponding solution is memo.
import React, {memo} from 'react';
const Foo = memo((props) = > {
console.log("render...")
return (
<p>{props.count}</p>)})Copy the code
Is that the end of it? Don’t. When using React Hook, our function component has the state feature, so our parent App is no longer just a class component. It can also be a function component. As follows:
const Foo = memo((props) = > {
console.log("render...")
return (
<p>{props.count}</p>)})const App = () = >{
const [count, setCount] = useState(0);
const [double, setDouble] = useState(1);
const cb = () = >{}
return (
<>
<Foo count={count} cb={cb}/>
<p>count: {count}, double: {double}</p>
<button onClick={()= >{setDouble(double*2)}}>Double</button>
</>)}Copy the code
You can see that even if we store the callback function with a variable, the child component is rendered repeatedly each time. This is because the App itself is a functional component that executes independently of each other. Cb is not retained every time. This is where useMemo comes in.
useMemo
Returns a new data
The syntax of useMemo is the same as useEffect. Unlike useEffect, however, useMemo is executed before rendering.
const App = () = >{
const [count, setCount] = useState(0);
const [double, setDouble] = useState(1);
const sum = useMemo(() = >{
return count + double;
}, [count])
return (
<>
<p>Count: {count}, double: {double}, sum: {sum}</p>
<button onClick={()= >{setDouble(double*2)}}>Double</button>
<button onClick={()= >{setCount(count+1)}}>count++</button>
</>)}Copy the code
When count and double change arbitrarily, sum changes as well. It has a bit of a computed feel in Vue.
Return a function
Going back to the previous question, if a parent component passes a function to a child component, how can we ensure that the child component does not repeat the rendering? UseMemo (()=>{return ()=>{}}, []) passes the empty array [] as the second argument, indicating that it will only be executed on the first time. This ensures that the function is the same and that the subcomponents will not be re-rendered.
const App = () = >{
const [count, setCount] = useState(0);
const [double, setDouble] = useState(1);
const cb = useMemo(() = >{
return () = >{
console.log("cb...")}}, [])return (
<>
<Foo count={count} cb={cb}/>
</>)}Copy the code
So, how does useCallback work? That is, when useMemo returns a function, we can use the shorthand useCallback. The following
const App = () = >{
const [count, setCount] = useState(0);
const [double, setDouble] = useState(1);
const cb = useCallback(() = >{
console.log("cb..")}, [])return (
<>
<Foo count={count} cb={cb}/>
</>)}Copy the code
The last
If there is any mistake or not precise place, please give correction, thank you very much. If you like or have some inspiration, welcome to like, to the author is also a kind of encouragement.