🎙️ preface
According to legend, react 17 has a very powerful function, that is, react hooks. In fact, react hooks is a bit similar to vue3's composition API. They all appear to improve development efficiency.
Then, in the following article, we will start from 0 to 1 to show you about react hooks and some common API s.
No more nonsense, start the react hook trip~
I 📻 summary
1. About React Hooks
- React Hooks is an optional function, which is usually compared with the class component;
- 100% backward compatibility, no destructive changes;
- Class components will not be replaced, and there are no plans to remove class components.
2. Meet React Hooks
(1) Review React functional components
Let's first review the class component and function component, as follows:
class component:
// class component class List extends React.Component { constructor() { super(props) } render() { const { List } = this.props return <ul>{list.map((item, index) => { return <li key={item.id}> <span>{item.title}</span> </li> })}</ul> } }
Function component:
// Function component function List(props) { const { list } = props return <ul>{list.map((item, index) => { return <li key={item.id}> <span>{item.title}</span> </li> })}</ul> }
(2) Features of function components
Function components are characterized by:
- No component instance;
- No life cycle;
- Without state and setState, only props can be received.
(3) class component problem
As mentioned above, the function component is a pure function, which can only receive props without any other functions. The class component has the above functions, but the class component has the following problems:
- Large components are difficult to split, reconstruct and test (that is, class is not easy to split);
- The same business logic is scattered into various methods, and the logic is chaotic;
- Reuse logic becomes complex, such as Mixins, HOC, Render Props.
Therefore, with the emergence of the above problems, there are React Hooks.
(4) React component
-
React components are easier to express with functions:
-
React advocates functional programming, that is, view=fn(props);
-
Functions are more flexible, easier to split and easier to test;
-
But function components are too simple and need to be enhanced -- so, with React Hooks.
II 🪕 Several Hooks
1,State Hook🗞️
(1) Let the function component implement state and setState
- The default function component has no state;
- The function component is a pure function, which is destroyed after execution, and the store cannot be stored;
- State Hook is required, that is, the store function is "hooked" into a pure function.
(2) Illustrate with examples
Suppose we now want to modify a set value by clicking the button. Now we use useState in hooks. The specific codes are as follows:
import React, { useState } from 'react' function ClickCounter() { // Deconstruction of arrays // useState is a Hook "Hook", the most basic Hook const [count, setCount] = useState(0) // Pass in an initial value, which can be numeric / string / array / object, etc const [name, setName] = useState('Monday Lab') // const arr = useState(0) // const count = arr[0] // const setCount = arr[1] function clickHandler() { setCount(count + 1) setName(name + '2021') } return <div> <p>You clicked {count} second {name}</p> <button onClick={clickHandler}>click</button> </div> } export default ClickCounter
The browser displays as follows:
In the above code, count is the value of a state, and setCount is a function to modify the state. Similarly, name is also a state value, and setName is a function that modifies the value of name.
If we use hooks to modify the state value, we only need to modify the final value in the form of const [count, setCount] = useState(0), instead of using it as complex as the class component.
For the above function, if the class component is used to implement it, the specific code is as follows:
import React from 'react' class ClickCounter extends React.Component { constructor() { super() // Define state this.state = { count: 0, name: 'Monday Lab' } this.clickHandler = this.clickHandler.bind(this) } render() { return <div> <p>You clicked {this.state.count} second {this.state.name}</p> <button onClick={this.clickHandler}>click</button> </div> } clickHandler() { // Modify state this.setState({ count: this.state.count + 1, name: this.state.name + '2021' }) } } export default ClickCounter
You can see that if you use the class component to solve the problem, you need to define state first, then define a function, and then use setState to modify the value. This seems to be a little troublesome.
I believe that here, we have felt the joy of hooks.
Next, let's summarize some knowledge about useState.
(3) useState Usage Summary
- useState(xxx) passes in the initial value and returns the array [state, setState];
- Get the value through state;
- Modify the value through setState(xxx).
(4) Hooks naming convention
- It is stipulated that all Hooks should start with use, such as useXxx;
- Custom Hook should also start with use;
- In places other than Hooks, try not to use useXxx, otherwise it is easy to cause misunderstanding.
2,Effect Hook🗞️
(1) Let function components simulate the lifecycle
- The default function component has no life cycle;
- Function component is a pure function, which is destroyed after execution, and it cannot realize the life cycle by itself;
- Use Effect Hook to "hook" the life cycle into pure functions.
(2) Illustrate with examples
Similarly, we use the same example of useState to experience useEffect.
Let's first create a file under SRC components, named liftcycles js . The specific codes are as follows:
import React, { useState, useEffect } from 'react' function LifeCycles() { const [count, setCount] = useState(0) const [name, setName] = useState('Monday Lab') // Simulate the DidMount and DidUpdate of the class component useEffect(() => { console.log('Send a message here ajax request') }) function clickHandler() { setCount(count + 1) setName(name + '2020') } return <div> <p>You clicked {count} second {name}</p> <button onClick={clickHandler}>click</button> </div> } export default LifeCycles
Now, we're at app JS. The specific codes are as follows:
import React, { useState } from 'react'; import LifeCycles from './components/LifeCycles' function App() { const [flag, setFlag] = useState(true) return ( <div {flag && <LifeCycles/>} </div> ); } export default App;
At this time, the display effect of the browser is as follows:
As you can see, if only one function is passed in, useEffect simulates the two life cycle functions of DidMount and DidUpdate. Every time we click once, the browser will print once, that is, component loading and component updating are carried out together.
What if we want to separate component loading and component updating. Let's transform liftcycles JS code. The specific codes are as follows:
import React, { useState, useEffect } from 'react' function LifeCycles() { const [count, setCount] = useState(0) const [name, setName] = useState('Monday Lab') // Simulate the DidMount of the class component useEffect(() => { console.log('Finished loading') }, []) // The second parameter is [] (independent of any state) // Simulate DidUpdate of class component useEffect(() => { console.log('Updated') }, [count, name]) // The second parameter is the dependent state function clickHandler() { setCount(count + 1) setName(name + '2020') } return <div> <p>You clicked {count} second {name}</p> <button onClick={clickHandler}>click</button> </div> } export default LifeCycles
The browser displays as follows:
As you can see, it was loaded and updated once for the first time. When we click, it will be updated because it has been loaded.
Let's sort out how useEffect handles loading and updating respectively?
Looking at the above code, we can find that useEffect can also receive the second parameter. If the second parameter is null, it does not depend on any state, indicating the life cycle of componentDidMount. On the contrary, if the second parameter has no dependent value or receives a state dependent value, it simulates the life cycle of componentDidUpdate.
Speaking of this, some small partners may also want to ask, what about the life cycle related to destruction? Let's continue with liftcycles JS code. The details are as follows:
import React, { useState, useEffect } from 'react' function LifeCycles() { const [count, setCount] = useState(0) const [name, setName] = useState('Monday Lab') // Simulate the DidMount of the class component useEffect(() => { let timerId = window.setInterval(() => { console.log(Date.now()) }, 1000) // Returns a function // Simulate WillUnMount return () => { window.clearInterval(timerId) } }, []) function clickHandler() { setCount(count + 1) setName(name + '2020') } return <div> <p>You clicked {count} second {name}</p> <button onClick={clickHandler}>click</button> </div> } export default LifeCycles
You can see the above code. Here, we use the return function to simulate the life cycle of componentWillUnMount to destroy the tasks executed in each timer.
Here, I believe you have a certain understanding of useEffect. Now, let's make a summary of useEffect.
(3) useEffect Usage Summary
- Simulate componentDidMount - useEffect dependency [];
- Simulate componentDidUpdate - useEffect has no dependency or depends on [a, b];
- Simulate a function returned in componentWillUnMount - useEffect.
(4) useEffect has side effects on pure functions
- By default, when executing a pure function, you only need to enter parameters and return results without any side effects;
- The so-called side effects are the effects outside the function, such as setting global timing tasks;
- Components need side effects, so useEffect needs to be "hooked" into pure functions;
- Therefore, the side effect of useEffect is not a bad thing.
(5) Return function fn in useEffect
One noteworthy situation is that we simulate WillUnMount by returning a function, but the result of this simulation is not completely equal to WillUnMount. Now, we create a file under src|components and name it friendstatus js . The specific codes are as follows:
import React, { useState, useEffect } from 'react' function FriendStatus({ friendId }) { const [status, setStatus] = useState(false) // DidMount and DidUpdate useEffect(() => { console.log(`Start listening ${friendId} Online status`) // [special attention] // This is not exactly the same as WillUnMount // When props changes, that is, updates will also end listening // To be exact: the returned function will be executed before the next effect execution return () => { console.log(`End listening ${friendId} Online status`) } }) return <div> Good friend {friendId} Online status:{status.toString()} </div> } export default FriendStatus
App.js code is as follows:
import React, { useState } from 'react'; import FriendStatus from './components/FriendStatus' function App() { const [flag, setFlag] = useState(true) const [id, setId] = useState(1) return ( <div> <div> <button onClick={() => setFlag(false)}>flag = false</button> <button onClick={() => setId(id + 1)}>id++</button> </div> {flag && <FriendStatus friendId={id}/>} </div> ); } export default App;
At this time, the display effect of the browser is as follows:
As you can see, when you start the next monitor, you will end the previous monitor first and then start the next one. As can be seen in the figure, at the beginning, we were listening to a friend with id 1. When we want to click the id + + button to listen to friend 2, useEffect will first end the state of 1 and then let friend 2 go online.
At this time, we should pay attention to the fact that the function that ends listening executes the DidUpdate life cycle instead of the WillUnMount life cycle. At this time, the function is in the update state rather than the destroy state.
Based on the above, let's make a summary. The details are as follows:
- When useEffect depends on [], execute the return function fn when the component is destroyed, which is equal to the WillUnMount life cycle;
- When useEffect does not depend or depends on [a, b], the component is a function fn that executes return when updating; That is, FN will be executed before the next useEffect. At this time, the DidUpdate life cycle is simulated.
3. Other Hooks 🗞️
Above, we talked about two commonly used hooks → useState and useEffect. Next, let's look at some other less commonly used hooks.
(1)useRef
Ref is used to modify values and obtain DOM elements. Let's start with a code:
import React, { useRef, useEffect } from 'react' function UseRef() { const btnRef = useRef(null) // Initial value const numRef = useRef(0) numRef.current = 2; useEffect(() => { console.log(btnRef.current) // DOM node console.log(numRef.current); // Get value }, []) return <div> <button ref={btnRef}>click</button> </div> } export default UseRef
At this time, the display effect of the browser is:
As you can see, if btnRef is bound to ref={btnRef}, btnRef Current gets the current DOM node.
In another case, you can locate numRef. If we assign a value to it and modify it, then numRef The final value of current is the modified value.
(2)useContext
Many times, we often need to switch some static attributes, such as theme color switching. At this time, the context needs to be used for processing. In hook, useContext can handle this matter. Let's start with a demo code:
import React, { useContext } from 'react' // Theme color const themes = { light: { foreground: '#000', background: '#eee' }, dark: { foreground: '#fff', background: '#222' } } // Create Context const ThemeContext = React.createContext(themes.light) // Initial value function ThemeButton() { const theme = useContext(ThemeContext) return <button style={{ background: theme.background, color: theme.foreground }}> hello world </button> } function Toolbar() { return <div> <ThemeButton></ThemeButton> </div> } function App() { return <ThemeContext.Provider value={themes.dark}> <Toolbar></Toolbar> </ThemeContext.Provider> } export default App
The browser displays as follows:
Now it belongs to the theme of black background. If we want to switch to the theme with white background, we just need to change the bottom code value={themes.dark} to value={themes.light}.
(3)useReducer
In Redux, we will use reducer, but useReducer is a little different from redux. useReducer is the status of a single component
Let's start with a code:
import React, { useReducer } from 'react' const initialState = { count: 0 } const reducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 } case 'decrement': return { count: state.count - 1 } default: return state } } function App() { // Much like const [count, setCount] = useState(0) const [state, dispatch] = useReducer(reducer, initialState) return <div> count: {state.count} <button onClick={() => dispatch({ type: 'increment' })}>increment</button> <button onClick={() => dispatch({ type: 'decrement' })}>decrement</button> </div> } export default App
The browser displays as follows:
We put the value to be changed into the reducer, and then pass the reducer to useRouter. Finally, the value is bound through dispatch.
Let's sort out the differences between useproducer and redux:
- useReducer is an alternative to useState, which is used to handle complex changes in state;
- useReducer is a single component state management, and props is also required for component communication;
- redux is a global state management, where multiple components share data.
(4)useMemo
useMemo is part of performance optimization in hooks. When we do not use useMemo, the state is like this. Look at the following code:
import React, { useState } from 'react' // Subcomponents function Child({ userInfo }) { console.log('Child render...', userInfo) return <div> <p>This is Child {userInfo.name} {userInfo.age}</p> </div> } // Parent component function App() { console.log('Parent render...') const [count, setCount] = useState(0) const [name, setName] = useState('Monday Lab') const userInfo = { name, age: 20 } return <div> <p> count is {count} <button onClick={() => setCount(count + 1)}>click</button> </p> <Child userInfo={userInfo}></Child> </div> } export default App
At this time, the printing effect of the console is as follows:
You can see that every time we click once, the Child sub component will reprint whether the state value is updated or not. This is more performance consuming for programs. Therefore, we should use useMemo to prevent Child events from printing frequently and improve the performance of the program. Code modification is as follows:
import React, { useState, memo, useMemo } from 'react' // Subcomponents // Similar to class PureComponent, shallow comparison of props const Child = memo(({ userInfo }) => { console.log('Child render...', userInfo) return <div> <p>This is Child {userInfo.name} {userInfo.age}</p> </div> }) // Parent component function App() { console.log('Parent render...') const [count, setCount] = useState(0) const [name, setName] = useState('Monday Lab') // Using useMemo to cache data depends on [name] // When name is updated, {name, age: 18} the cache will be invalidated const userInfo = useMemo(() => { return { name, age: 18 } }, [name]) return <div> <p> count is {count} <button onClick={() => setCount(count + 1)}>click</button> </p> <Child userInfo={userInfo}></Child> </div> } export default App
At this time, the display effect of the browser is as follows:
You can see that the Child component will not be printed unless it is updated for the first time. How is it used? First, we need to wrap the sub components with memo. Then, we use useMemo to cache data. At the same time, the data depends on [name]; It is worth noting that if it does not depend on [name], it will not take effect.
Now let's summarize useMemo as follows:
- React will update all sub components by default;
- class components are optimized using shouldComponentUpdate and PureComponent;
- Hooks uses useMemo for optimization, but the optimization principle is the same as that of class components.
(5)useCallback
We talked about using useMemo to cache data. Now let's talk about useCallback. useCallback is mainly used to cache functions.
Suppose we don't use useCallback now to run the following code:
import React, { useState, memo, useMemo, useCallback } from 'react' // Sub component, memo is equivalent to PureComponent const Child = memo(({ userInfo, onChange }) => { console.log('Child render...', userInfo) return <div> <p>This is Child {userInfo.name} {userInfo.age}</p> <input onChange={onChange}></input> </div> }) // Parent component function App() { console.log('Parent render...') const [count, setCount] = useState(0) const [name, setName] = useState('Monday Lab') // Cache data with useMemo const userInfo = useMemo(() => { return { name, age: 21 } }, [name]) function onChange(e) { console.log(e.target.value) } return <div> <p> count is {count} <button onClick={() => setCount(count + 1)}>click</button> </p> <Child userInfo={userInfo} onChange={onChange}></Child> </div> } export default App
The printing effect of the browser is as follows:
You can see that if useCallback is not used, it will always print out the sub components, that is, the onChange event will always run. Now, let's transform the onChange event as follows:
// Caching functions with useCallback const onChange = useCallback(e => { console.log(e.target.value) }, [])
At this point, let's look at the effect of the browser:
As you can see, when useCallback is added and Child is not updated, it will not be updated again!
Next, let's make a summary of useMemo and useCallback:
- useMemo cache data;
- useCallback cache function;
- Both are common optimization strategies of React Hooks.
4. Customize Hook 🗞️
(1) Why use a custom Hook
- Used to encapsulate general functions;
- Can develop and use third-party Hooks;
- Custom Hook brings unlimited scalability and decouples code.
(2) Illustrate with examples
Suppose we want to encapsulate a useAxios now, how do we implement it?
First, we need to install axios in the project. The specific code is as follows:
npm i axios --save
Next, we create a new file in the src|customHooks folder of the project, named useaxios js . The specific codes are as follows:
import { useState, useEffect } from 'react' import axios from 'axios' // Encapsulates a custom Hook that axios sends network requests function useAxios(url) { // Whether the loading simulation is currently waiting const [loading, setLoading] = useState(false) // Data returned when the request is successful const [data, setData] = useState() // Some information returned when the request fails const [error, setError] = useState() useEffect(() => { // Sending network requests using axios setLoading(true) axios.get(url) // Send a get request .then(res => setData(res)) .catch(err => setError(err)) .finally(() => setLoading(false)) }, [url]) return [loading, data, error] } export default useAxios
Continue. In the src|components folder of the project, create a new file named customhookusage js . This component is mainly used to use the useAxios hook customized above. The specific codes are as follows:
import React from 'react' import useAxios from '../customHooks/useAxios' function App() { const url = 'http://localhost:3000/' // Array deconstruction const [loading, data, error] = useAxios(url) if (loading) return <div>loading...</div> return error ? <div>{JSON.stringify(error)}</div> : <div>{JSON.stringify(data)}</div> } export default App
Finally, we are in the project's app JS to use it. The specific codes are as follows:
import React, { useState } from 'react'; import CustomHookUsage from './components/CustomHookUsage' function App() { return ( <div> {<CustomHookUsage/>} </div> ); } export default App;
At this point, let's take a look at the display effect of the browser:
As you can see, first of all, when waiting, it will appear loading first. After that, if the wait ends and the request succeeds, data will be returned. Of course, we do not demonstrate the case of error here. You can change the url to a non-existent request address to test the error.
(3) Summary
After reading the above demonstration code, let's make a summary:
- Custom hook is essentially a function, starting with use;
- useState, useEffect or other Hooks can be used internally normally;
- User defined return results with unlimited formats;
Here we recommend two third-party custom Hook libraries:
- react-hooks: https://nikgraf.github.io/react-hooks/
- hooks: https://github.com/umijs/hooks
5. Two important rules of Hooks 🗞️
(1) Hooks usage specification
-
Hooks can only be used in react function components and custom hooks, but not elsewhere. For example, class components and ordinary functions;
-
It can only be used for top-level code, and Hooks cannot be used in loops and judgments;
-
The eslint plugin react hooks plug-in in eslint can help you use react hooks. The configuration is as follows:
// ESLint profile { "plugins": [ // ... Omit line N here "react-hooks" ], "rules": { // ... Omit line N here "react-hooks/rules-of-hooks": "error", // Check Hook rules "react-hooks/exhaustive-deps": "warn" // Check the dependency of effect } }
(2) Hooks call order must be consistent
In react-hooks, calling sequence is a key point that needs special attention. Why?
Let's use a piece of code to demonstrate:
import React, { useState, useEffect } from 'react' function Teach({ couseName }) { // Function component, pure function, destroyed upon execution // Therefore, no matter component initialization (render) or component update (re render) // Will re execute this function to get the latest component // This is different from the class component // render: initialize the value 'Zhang San' of state // Re render: read the state value 'Zhang San' const [studentName, setStudentName] = useState('Zhang San') // if (couseName) { // const [studentName, setStudentName] = useState('zhang San ') // } // render: initialize the value 'double crossing' of state // Re render: read the value 'double crossover' of state const [teacherName, setTeacherName] = useState('Monday Lab') // if (couseName) { // useEffect(() => { // //Simulated student check-in // localStorage.setItem('name', studentName) // }) // } // render: add effect function // Re render: replace the effect function (the internal function will also be redefined) useEffect(() => { // Simulated student check-in localStorage.setItem('name', studentName) }) // render: add effect function // Re render: replace the effect function (the internal function will also be redefined) useEffect(() => { // The simulation begins console.log(`${teacherName} Start class, students ${studentName}`) }) return <div> Course:{couseName}, Lecturer:{teacherName}, student:{studentName} </div> } export default Teach
The above code demonstrates a function component. For function components, it is a pure function, which is usually destroyed after execution. Therefore, whether it is component initialization (render) or component update (re render), this function will be re executed to obtain the latest component, which is different from the class component.
Therefore, if we wrap the const [studentName, setStudentName] = useState('zhang San ') above with an IF statement, it will be blocked during execution. At the same time, if you go down in the following order, you may assign the value of Zhang San to teacherName. Therefore, in react hooks, we can't use react hooks in loops and judgments, otherwise it will easily lead to disordered call order.
Based on the above analysis, let's make a summary of this rule:
- Whether it is render or re render, the Hooks call order must be consistent;
- If Hooks appear in the cycle and judgment, the order cannot be guaranteed;
- Hooks depends heavily on the call order! Important!
III ⌨️ Logical reuse of react hooks components
1. Logical reuse of class components
class components have three forms of logical reuse. namely:
- Mixin
- High order component HOC
- Render Prop
Let's talk about their respective shortcomings.
(1)Mixin
- The source of variable scope is unclear
- Duplicate attribute name
- Too many mixins will lead to sequence conflict
(2) High order component HOC
- There are too many nested component levels, which is difficult to render and debug
- HOC will hijack props, which must be strictly standardized and prone to omissions
(3)Render Prop
- Learning costs are high and difficult to understand
- You can only pass pure functions, which are limited by default
After understanding the disadvantages of the three class components, now let's see how to use Hooks for component logical reuse.
2. Logical reuse of components using Hooks
The essence of using hooks to enable logical reuse of components is to customize hooks. Let's use an example to show.
(1) Examples
First, we create a new file in the src|customHooks folder of the project, named usemouseposition js . The specific codes are as follows:
import { useState, useEffect } from 'react' function useMousePosition() { const [x, setX] = useState(0) const [y, setY] = useState(0) useEffect(() => { function mouseMoveHandler(event) { // event.clientX can get the location of the abscissa and ordinate of the mouse setX(event.clientX) setY(event.clientY) } // Binding event document.body.addEventListener('mousemove', mouseMoveHandler) // Unbinding event return () => document.body.removeEventListener('mousemove', mouseMoveHandler) }, []) return [x, y] } export default useMousePosition
Continue. In the src|components folder of the project, create a new file named customhookusage js . This component is mainly used to use the logic in useMousePosition above. The specific codes are as follows:
import React from 'react' import useMousePosition from '../customHooks/useMousePosition' function App() { const [x, y] = useMousePosition() return <div style={{ height: '500px', backgroundColor: '#ccc' }}> <p>Mouse position {x} {y}</p> </div> } export default App
Finally, we are in the project's app JS to use it. The specific codes are as follows:
import React, { useState } from 'react'; import CustomHookUsage from './components/CustomHookUsage' function App() { return ( <div> {<CustomHookUsage/>} </div> ); } export default App;
At this point, let's take a look at the display effect of the browser:
Using hooks for component logical reuse is much simpler and more flexible than class components. Let's sort out the benefits of hooks for component logic reuse.
(2) Benefits
The benefits of Hooks for component logic reuse:
- Fully comply with Hooks' original rules, no other requirements, easy to understand and remember;
- Variable scope is clear;
- Component nesting will not occur.
IV 📀 React Hooks considerations
1. useState initialization value, valid only for the first time
Let's first look at a piece of code, as follows:
import React, { useState } from 'react' // Subcomponents function Child({ userInfo }) { // render: initialize state // Re render: only the initialized state value will be restored, and no new value will be reset // Can only be modified with setName const [ name, setName ] = useState(userInfo.name) return <div> <p>Child, props name: {userInfo.name}</p> <p>Child, state name: {name}</p> </div> } function App() { const [name, setName] = useState('Monday') const userInfo = { name } return <div> <div> Parent <button onClick={() => setName('Tuesday')}>setName</button> </div> <Child userInfo={userInfo}/> </div> } export default App
At this time, the display effect of the browser is as follows:
As you can see, when we click, only UseInfo is changed The value of name, that is, the value of the first Child. At this time, some small partners may have doubts and say, UseInfo The value of name is assigned to name, so why doesn't the second Child change.
The reason is that for useState, we always set an initialization value for state at the beginning. Even if the data is updated, only the first value is valid, and the updated value will not be assigned to it. Therefore, the name value in the second Child below is always Monday.
2. state cannot be modified inside useEffect
Let's simulate a piece of code:
import React, { useState, useEffect } from 'react' function UseEffectChangeState() { const [count, setCount] = useState(0) // Simulate DidMount useEffect(() => { console.log('useEffect...', count) // Timed task const timer = setInterval(() => { console.log('setInterval...', count) setCount(count + 1) }, 1000) // Clear scheduled tasks return () => clearTimeout(timer) }, []) // Dependency is [] // When the dependency is []: re render will not re execute the effect function // When there is no dependency: re render will re execute the effect function return <div>count: {count}</div> } export default UseEffectChangeState
Now let's look at the effect of the browser:
As you can see, 1234 should be printed incrementally according to our expectations, but it is not displayed in the end. The initial value printed in timer is always 0, that is, this state does not work in useEffect.
Therefore, we need to pay special attention to this in our daily development. It is possible that there are bug s written in the code.
Now that the problem has arisen, we need to think about its solution. The transformation is as follows:
import React, { useState, useRef, useEffect } from 'react' function UseEffectChangeState() { const [count, setCount] = useState(0) // Simulate DidMount const countRef = useRef(0) useEffect(() => { console.log('useEffect...', count) // Timed task const timer = setInterval(() => { console.log('setInterval...', countRef.current) // setCount(count + 1) setCount(++countRef.current) }, 1000) // Clear scheduled tasks return () => clearTimeout(timer) }, []) // Dependency is [] // When the dependency is []: re render will not re execute the effect function // No dependency: re render will re execute the effect function return <div>count: {count}</div> } export default UseEffectChangeState
The browser displays as follows:
Looking at the above code, we can find that by putting countRef outside useEffect and using useRef, we can modify the value of state to achieve our ultimate goal.
3. useEffect may have a dead loop
Suppose we want to use axios to send network requests and want to add some configuration to the url, then we may handle it as follows:
import { useState, useEffect } from 'react' import axios from 'axios' // Encapsulates a custom Hook that axios sends network requests function useAxios(url, config = {}) { const [loading, setLoading] = useState(false) const [data, setData] = useState() const [error, setError] = useState() useEffect(() => { // Sending network requests using axios setLoading(true) axios(url, config) // Send a get request .then(res => setData(res)) .catch(err => setError(err)) .finally(() => setLoading(false)) }, [url, config]) return [loading, data, error] } export default useAxios
It seems reasonable, but when config uses the reference data type, that is, config = {} or confgi = [], useEffect will have an endless loop and send requests indefinitely. Therefore, when we want to pass values to axios, we can only pass values of basic data types. The modification code is as follows:
import { useState, useEffect } from 'react' import axios from 'axios' // Encapsulates a custom Hook that axios sends network requests function useAxios(url) { const [loading, setLoading] = useState(false const [data, setData] = useState() const [error, setError] = useState() useEffect(() => { // Sending network requests using axios setLoading(true) axios.get(url) // Send a get request .then(res => setData(res)) .catch(err => setError(err)) .finally(() => setLoading(false)) }, [url]) return [loading, data, error] } export default useAxios
In this way, there will be no data request sent to the interface in an endless loop.
V 🔍 Conclusion
In the above article, we first understand the basic concept of hooks. Then, we focus on state hook and effect hook. At the same time, we also briefly understand several other hooks. Finally, the contribution of react hooks in component logic reuse and some matters needing attention are discussed.
Here, the introduction of react hooks is over! I wonder if you have some new understanding of react hooks?
🐣 One More Thing
(: recommended in previous periods)
The react column pokes here https://juejin.cn/column/7018019097656950815
(: Ding
- Pay attention to the official account of Monday's research room. For the first time, you should pay attention to quality articles, and even more, "offer is coming".
- If you think this article is helpful to you, remember to leave a footprint jio before you go~~ 😉
- The above is the whole content of this article! See you next time! 👋👋👋