preface
When I first came into contact with the front-end MV* framework, React was the first thing I came into contact with. At that time, IT was still in the so-called “Dachang”, but it was still early at that time, so I came into contact with version 14. Later, after leaving here, I entered a start-up company.
Recently, I plan to learn React again. I have read a lot of articles about Hooks on the Internet. After systematic study and summary, I have created this article
What is the Hooks
- React-hooks are the react-hooks API that allows you to use state and other features of React without writing classes
- Hooks are functions that allow you to “Hook” React State and lifecycle features into function components
Why use Hooks
Disadvantages of class components
- Reusing state logic between components is difficult
- Complex components become difficult to understand
- Hard to understand class
You have to understand how this works in JS, which is quite different from other languages, and don’t forget to bind event handlers. There is no stable syntax proposal, and the code is very redundant
Hooks solve the above problem. In addition, there are some other things to do:
- Increase code reusability and logic to make up for the defects of stateless components with no life cycle and no data management state
- The react-hooks idea is closer to functional programming. Es6. Which is the result of which are Hooks that need to write the render function in the class declaration cycle.
Hooks do not contain damaging changes
- Completely optional: You can try a Hook in some components without rewriting any existing code, but you don’t have to learn or use it now if you don’t want to
- 100% backward compatibility: Hooks do not eat any destructive changes
- Now available: Hooks are released in V16.8
Rule for using Hooks
- Use hooks only at the top level; do not call hooks in loops, conditions, or nested functions
Make sure to always call them at the top of your React function. By following this rule, you can ensure that hooks are called in the same order every time you render. This allows React to keep the hook state correct between multiple useState and useEffect calls
- Only the React function calls the Hook
Instead of calling a Hook in a normal JS function, you can
- Call a Hook in the React function component
- Call other hooks in a custom Hook
useState
const [state , setState] = useState(initialState)
- UseState takes one parameter, initialState (which can be a function that returns a value), which can be of any data type and is generally used as the default
- UseState returns an array whose first argument is the state we need to use and whose second argument is a function that changes the state (same as this.setstate).
useEffect
Effect Hook allows you to perform side effects (fetch data, check Settings, and manually change the DOM in the React component) in function components
useEffect(fn , array)
UseEffect is executed once after the first rendering, with a second parameter to simulate some life cycle of the class
UseEffect Hook can be regarded as the combination of three functions componentDidMount componentDidUpdate and componentWillUnmount.
UseEffect implementation componentDidMount
UseEffect equals componentDidMount if the second argument is an empty array
import React , { useState , useEffect } from 'react'
function Example(){
const [count , setCount] = useState(0);
useEffect(() = >{
console.log("I'm only going to do it after the component is first mounted."); } []);return (
<div>
<p>xxxxx {count} </p>
<button onClick={()= > setCount(count +1)}}>Click me</button>
</div>)}export default Example;
Copy the code
After the page is rendered,useEffect will be executed once and log will be printed. When the button is clicked to change the state and the page is rendered again,useEffect will not be executed.
UseEffect implementation componentDidUpdate
If the second parameter is not passed,useEffect is executed on the first render and every update
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() = > {
console.log("I'll do it after the first component is mounted and when I re-render.");
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()= > setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
Copy the code
On the first render, a useEffect is executed, printing out “I will do this after the first component is mounted and when I re-render”. When the button is clicked, the state is changed, the page is re-rendered, useEffect is executed, printing “I will execute after the first component is mounted and when the component is re-rendered”.
UseEffect implementation componentWillUnmount
Effect returns a function that React calls when a clean operation is performed
useEffect(() = > {
console.log("Subscribe to some events");
return () = > {
console.log("Perform cleanup operation")}}, []);Copy the code
Note: ‘Perform cleanup’ is printed not only for component destruction, but also for every re-render. The reason is clearly explained on the website. Check it out
Control the useEffect execution
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
const [number, setNumber] = useState(1);
useEffect(() = > {
console.log("I'm only going to execute when Cout changes.");
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()= > setCount(count + 1)}>Click cout</button>
<button onClick={()= > setNumber(number + 1)}>Click number</button>
</div>
);
}
export default Example;
Copy the code
If there is an element in the array, the element’s value has been changed and then rerendered.
Use multiple Useeffects for separation of concerns
One of the goals of using hooks is to solve the problem that class lifecycle functions often contain unrelated logic, but separate the related logic into several different methods.
import React, { useState, useEffect } from "react";
function Example() {
useEffect(() = > {
A / / logic
});
useEffect(() = > {
2 / / logic
});
useEffect(() = > {
3 / / logic
});
return (
<div>The use of useEffect</div>
);
}
export default Example;
Copy the code
Hooks allow us to separate code according to its purpose, rather than life cycle functions. React will call each effect in the component in the order in which it is declared
UseEffect uses asynchronous functions
UseEffect cannot use async await directly
Incorrect usage:
/* effect does not support direct async await*/
useEffect(async() = > {/* Request data */
const res = await getData()
},[])
Copy the code
The useEffect callback argument returns a clean up function that clears side effects. Therefore, there is no way to return a Promise and still less async/await
Usage Method 1 (recommended)
const App = () = > {
useEffect(() = >{(async function getDatas() {
await getData();
})();
}, []);
return <div></div>;
};
Copy the code
Usage Method 2
useEffect(() = > {
const getDatas = async() = > {const data = awaitgetData(); setData(data); }; getDatas(); } []);Copy the code
What does the useEffect do
By using the useEffect Hook, you can tell the React component that it needs to do something after rendering. React will save the function you passed and call it after performing a DOM update.
Why is useEffect called inside a component
Putting useEffect inside the component allows us to access the state variable (or other props) directly in effect. We don’t need a special API to read it – it’s already stored in function scope. Hook uses the MECHANISM of JS closures rather than introducing a specific React API when JS already provides a solution
useContext
concept
const value = useContext(MyContext) ;
Receives a context object (the return value of React. CreateContext) and returns the current value of the context. When the most recent
update is made to the component’s upper layer, the Hook triggers a re-rendering and uses the latest context value passed to MyContext Provider. Even if the ancestor used React. Memo or shouldComponentUpdate will be rerendered when the component itself uses useContext.
Remember that the argument to useContext must be the context object itself:
- Right: useContext (MyContext)
- Error: useContext (MyContext. Consumer)
- Error: useContext (MyContext. The Provider)
The sample
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// Create two contexts
export const UserContext = React.createContext();
export const TokenContext = React.createContext();
ReactDOM.render(
<UserContext.Provider value={{ id: 1.name: "chimmy", age: "20}} ">
<TokenContext.Provider value="I am a token">
<App />
</TokenContext.Provider>
</UserContext.Provider>.document.getElementById("root"));Copy the code
app.js
import React, { useContext } from "react";
import { UserContext, TokenContext } from "./index";
function Example() {
let user = useContext(UserContext);
let token = useContext(TokenContext);
console.log("UserContext", user);
console.log("TokenContext", token);
return (
<div>name:{user? .name},age:{user?.age}</div>
);
}
export default Example;
Copy the code
prompt
If you’re already familiar with the Context API before you hit the Hook, it should make sense, UseContext (MyContext) is equivalent to static contextType = MyContext or < myContext.consumer > in the class component.
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 the context for the lower component
useReducer
concept
const [state , dispatch] = useReducer(reducer , initialArg , init) ;
An alternative to useState. It receives a Reducer of the form (state,action) => newState and returns the current state and its accompanying dispatch method.
UseReducer can be more useful than useState in some situations, 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 can optimize performance for components that trigger deep updates because you can pass dispatches to child components instead of callbacks.
Note:
React ensures that the identity of the Dispatch function is stable and does not change when a component is rerendered, which is why it is safe to omit Dispatch from useEffect or useCallback’s dependency list
Example:
import React, { useReducer } from "react";
export default function Home() {
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, counter: state.counter + 1 };
case "decrement":
return { ...state, counter: state.counter - 1 };
default:
returnstate; }}const [state, dispatch] = useReducer(reducer, { counter: 0 });
return (
<div>
<h2>Home Current count: {state.counter}</h2>
<button onClick={(e)= > dispatch({ type: "increment" })}>+1</button>
<button onClick={(e)= > dispatch({ type: "decrement" })}>-1</button>
</div>
);
}
Copy the code
useCallback
Concept:
const memoizedCallback = useCallback(
() = > {
doSomething(a, b);
},
[a, b],
);
Copy the code
Returns a memoized callback function
Passing the inline callback function and the array of dependencies as arguments to useCallback returns the Memoized version of the callback function, which is updated only when a dependency changes. This is useful when you pass callback data to child components that are optimized and use reference equality to avoid unnecessary rendering
Example:
import React, { useState } from "react";
/ / child component
function Childs(props) {
console.log("The child component is rendered.");
return (
<>
<button onClick={props.onClick}>Change the title</button>
<h1>{props.name}</h1>
</>
);
}
const Child = React.memo(Childs);
function App() {
const [title, setTitle] = useState("This is a title.");
const [subtitle, setSubtitle] = useState("I am a subtitle");
const callback = () = > {
setTitle("The title has changed.");
};
return (
<div className="App">
<h1>{title}</h1>
<h2>{subtitle}</h2>
<button onClick={()= >SetSubtitle (" subtitle changed ")}> Change the subtitle</button>
<Child onClick={callback} name="Peach peach" />
</div>
);
}
Copy the code
When the ‘change subtitle’ button is clicked, the subtitle changes to ‘subtitle changed’ and the console prints’ child rendered ‘. This proves that the child has been rerendered, but the child has not changed at all. This re-rendering of the Child component is superfluous.
Looking for a reason
Before solving the problem, first of all, what is the cause of the problem
There are three ways to re-render a component:
- Either the state of the component itself changes
- Either the parent component rerenders, causing the child component to rerender, but the props of the parent component does not change
- Either the parent component rerenders, causing the child component to rerender, but the props passed by the parent component changes
Check from the above reasons:
The first one is obvious, and does not change the state of the child component when the button is clicked
We use react. memo to solve this problem
When the parent component rerenders, the porps passed to the Child component have changed, and the two properties passed to the Child component, name and onClick, will not change. Why does the callback passed to onClick change? Each time the function component is re-rendered, the callback function must have changed, causing the child component to be re-rendered.
Solve the problem with useCallback
const callback = () = > {
doSomething(a, b);
}
const memoizedCallback = useCallback(callback, [a, b])
Copy the code
Passing the function and its dependencies as arguments to useCallback will return the Memoizedversion of the callback function, which will be updated only if the dependency changes.
Make sure to modify the callback function passed to the Child component as follows:
const callback = () = > { setTitle("The title has changed."); };
// Use useCallback to do the memory callback and pass the memory callback to Child
<Child onClick={useCallback(callback, [])} name="Peach peach" />
Copy the code
This way we can see that rendering only happens on the first render. It will no longer print when changing subheadings and titles
useMemo
concept
const cacheSomething = useMemo(create , deps)
- The first argument to create is a function whose return value is cached
- The second parameter of dePS is an array that stores the current useMemo dependencies. When the function component is executed next time, it will compare the state of the DEPS dependencies to see if there is any change. If there is any change, useMemo will be executed again to obtain the new cache value.
- CacheSomething return value. The return value of create is executed. If a dePS dependency changes, the return value of create is executed again
UseMemo principle
UseMemo records the return value of the last create execution and binds it to the fiber object corresponding to the function component. The cached value will remain as long as the component is not destroyed, but if there is a change in the DEPS, create will be re-executed and the return value will be recorded to Fiber as the new value On the object.
Example:
function Child(){
console.log("The child component is rendered.")
return <div>Child</div>
}
const Child = memo(Child)
function APP(){
const [count, setCount] = useState(0);
const userInfo = {
age: count,
name: 'jimmy'
}
return <Child userInfo={userInfo}>
}
Copy the code
When the function component is rerendered,userInfo is a new object each time, causing the Child component to be rerendered regardless of whether count has changed
The following one does not return a new object until the count changes
function Child(){
console.log("The child component is rendered.")
return <div>Child</div>
}
function APP(){
const [count, setCount] = useState(0);
const userInfo = useMemo(() = > {
return {
name: "jimmy".age: count
};
}, [count]);
return <Child userInfo={userInfo}>
}
Copy the code
In fact, useMemo does more than that. According to the official documentation, you can put some expensive computing logic into useMemo and update it only when the dependency value changes.
import React, {useState, useMemo} from 'react';
// The function that computes and is expensive
function calcNumber(count) {
console.log("CalcNumber recalculation");
let total = 0;
for (let i = 1; i <= count; i++) {
total += i;
}
return total;
}
export default function MemoHookDemo01() {
const [count, setCount] = useState(100000);
const [show, setShow] = useState(true);
const total = useMemo(() = > {
return calcNumber(count);
}, [count]);
return (
<div>
<h2>Calculate the sum of numbers: {total}</h2>
<button onClick={e= > setCount(count + 1)}>+1</button>
<button onClick={e= >setShow(! The show)} > show switch</button>
</div>)}Copy the code
When the show toggle button is clicked, the calcNumber function that calculates the sum does not render, only when count changes
Summary of useCallback and useMemo
UseCallback and useMemo cache a function and cache the return of a function You can optimize the current component as well as its subcomponents, optimizing the current component mainly through memoize to cache some of the more complex computational logic, although there is no need to use useMemo for simple calculations
We can implement useCallback flexibly by defining the return value of useMemo as returning a function
UseCallback (fn, deps) equivalent to useMemo(()=> fn, deps)
useRef
const refContainer = useRef(initialValue);
UseRef returns a mutable ref object whose.current property is initialized as the passed parameter, and the returned REF object remains unchanged for the lifetime of the component
UseRef for dom
UseRef has a parameter that can be used as the initial value of cached data, the return value can be marked by the DOM element ref, and the marked element node can be retrieved.
import React, { useRef } from "react";
function Example() {
const divRef = useRef();
function changeDOM() {
// Get the entire div
console.log("The whole div", divRef.current);
// Get the div class
console.log("Div class." ", divRef.current.className);
// Get the div custom attribute
console.log("Div custom attribute", divRef.current.getAttribute("data-clj"));
}
return (
<div>
<div className="div-class" data-clj="I'm a custom attribute for div." ref={divRef}>I am a div</div>
<button onClick={(e)= >The DOM changeDOM ()} ></button>
</div>
);
}
export default Example;
Copy the code
UseRef caches data
Another important function of useRef is to cache data. We know that the useState useReducer can save the current data source. But useRef is a great choice if they update the data source’s function execution rewrite that results in the entire component being reexecuted to render, and if you declare variables inside the function component, the next update will reset, if you want to save data quietly without triggering function updates
import React, { useRef, useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
const numRef = useRef(count);
useEffect(() = > {
numRef.current = count;
}, [count]);
return (
<div>
<h2>Count last value: {numref.current}</h2>
<h2>Count: {count}</h2>
<button onClick={(e)= > setCount(count + 10)}>+10</button>
</div>
);
}
export default Example;
Copy the code
When the ref object’s contents change, UseRef does not tell you that the.current property does not cause the component to re-render, so in the box above, numref.current has changed, but the page still shows the last value, and when it is updated, it will show the last value.