What is the Hooks
Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class.
Why Hooks
The code is readable and easy to maintain
1.hooks use in function components, don’t have to maintain complex life cycles, don’t have to worry about this pointing problems
The Function component can also maintain its own state without worrying about the issues this points to during component communication.
2. Better way to reuse logic
See the section on custom hooks in this chapter for details about how to use code in React (high order components, render props)
Improve development efficiency
Let’s compare the code that implements the same function using the class component with the function component using hooks,
1.Class component version
import React from 'react'; class Person extends React.Component { constructor(props) { super(props); This. state = {username: ""}; } componentDidMount() {console.log(' componentWillUnmount ') {console.log(' componentDidMount ')} componentWillUnmount() {console.log(' componentDidMount ')} componentDidUpdate(prevProps, prevState) { if(prevState.username ! }} render() {return (<div> <p> welcome {state.username}</p> <input type="text" placeholder="input a username" onChange={(event) => this.setState({ username: event.target.value)})}></input> </div> ); }}Copy the code
Version 2. The Hooks
Import React, {useState, useEffect} from 'React '; Export const Person = () => {const [name, setName] = useState(" 表 明"); UseEffect (() => {console.log(' Actions to do after component is mounted ') return () => {console.log(' Actions to do after component is mounted ')}}, []); UseEffect (() = > {the console. The log (' updated operating components')}, [name]); Return (<div> <p> welcome {name}</p> <input type="text" placeholder="input a username" onChange={(event) => setName( event.target.value)}></input> </div> ) }Copy the code
The Hooks version simplifies a lot of code and makes development significantly more efficient once you’re familiar with it.
How to use Hooks
Based API Hooks
UseState (Key Grasp)
1. The parameters:
- Constants: defined when the component is initialized
import React, { useState } from 'react'; Const [count, setCount] = useState(0); const [count, setCount] = useState(0); const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }Copy the code
-
Function: the function is executed only when rendering begins
The initialState parameter is only used in the initial rendering of the component and will be ignored in subsequent renderings. // If the initial state needs to be computed by a complex calculation, you can pass in a function that calculates and returns the initial state. // This function is only called during the initial render: const [count, setCount] = useState(() => { const initialState = someExpensiveComputation(props); return initialState; })Copy the code
2. The return value
UseState returns an array of length 2. The first item of the array is the variable defined (name your own) and the second item is the function that changes the first item (name your own). See the code above for an example.
useEffect
The Hook takes two arguments. The first argument is a function that contains imperative code that may have side effects, and the second argument is an array that controls whether or not the Effect wrapped function is executed. If the second argument is not passed, the Effect is executed every time the component is refreshed. Equivalent to a fusion of the componentDidMount and componentDidupdate life cycles in the Class component.
1. Basic usage
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Copy the code
2. Control the execution of functions
Similar to the code above, we pass useEffect a second parameter [count] so that it is executed only when count changes
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); UseEffect (() => {// Update the document title using the browser API document.title = 'You clicked ${count} times`; },[count]); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } import React, { useEffect } from 'react'; UseEffect (() => {console.log(" console.log(" console.log(" console.log ")},[]); Return (<div> Effect</div>); }Copy the code
3. Side effects that need to be removed
There are some side effects that need to be removed. Such as subscribing to external data sources. In this case, cleaning is very important to prevent memory leaks!
Example 1(cleared with each rendering) :
import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // Specify how to clean up after this effect: return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading... '; } return isOnline ? 'Online' : 'Offline'; }Copy the code
Example 2(cleared only when components are uninstalled) :
When we pass an empty array to the second argument, Effect is cleared only when the component is unmounted. UseEffect is equivalent to the class component’s componentDidMount and compinentWillUnmount combined.
import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // Specify how to clean up after this effect: return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; } []); if (isOnline === null) { return 'Loading... '; } return isOnline ? 'Online' : 'Offline'; }Copy the code
We should be flexible in daily use, but try to use the second parameter to control the execution of the function, which can optimize performance.
UseContext (important)
The Hook receives a context object (the return value of react. createContext) and returns the current value of the context. The current context value is determined by the < MyContext.provider > value prop of the upper-layer component closest to the current component.
1. Example:
const themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "# 222222"}}; // ThemeContext const ThemeContext = react.createcontext (themes.light); Function App() {function App() { Provider value={themes.dark}> <Toolbar /> </ themecontext.provider >); } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() {const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); }Copy the code
2. For details about how to implement the same logic for the Class component, seeReact Official document -Context
A simple example:
// Context allows us to pass values deep into the component tree without explicitly traversing each component. // Create a context for the current theme (" light "is the default). const ThemeContext = React.createContext('light'); Class App extends React.Component {render() {// Use a Provider to pass the current theme to the following component tree. // Any component, no matter how deep, can read this value. // In this case, we pass "dark" as the current value. return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); }} // Intermediate components no longer have to specify the theme to pass down. function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends react.ponent {// Specify contextType to read the current theme context. // React will find the nearest theme Provider and use its value. // In this case, the current theme value is "dark". static contextType = ThemeContext; render() { return <Button theme={this.context} />; }}Copy the code
‘Another version:
The useContext hook is also very simple. It allows us to use the Context in the function component, and it solves the problem of wrapping the Consumer component:
import React from 'react' const { Provider, Consumer} = react.createcontext (null) // createContext and expose Provider and Consumer export {Provider, Consumer} // Parent import React from 'React' import Son from './ Son 'import {Provider} from './context' class Father extends React.Component { constructor(props) { super(props) } state = { info: 'info from father', } render() { return ( <Provider value={this.state.info}> <div> <Son /> </div> </Provider> ) } } export default FatherCopy the code
Inside the class component, we need to wrap the Consumer component to get the value from the Context:
import React from 'react' import { Consumer } from './context' class Son extends React.Component { constructor(props) { Super (props)} render() {return (<Consumer> {(info) => (div> <p> <div>)} </Consumer> ) } } export default SonCopy the code
With useContext, this is all you need:
Import React from 'React' funcion Son() {const info = useContext(Context) render() {return (<p> parent :{info}</p>)}} export default SonCopy the code
UseContext (Context)
({vlaue} => {})
UseReducer (important)
An alternative to useState. It receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method (very similar to the redux usage).
const [state, dispatch] = useReducer(reducer, initialArg, init);
Copy the code
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.
Parameters:
- The first parameter is the Reducer pure function
- The second parameter is the initial state
- The third argument can change the initial state to init(initialArg)
1. Basic usage
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
Copy the code
useCallback
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.
- Common application scenarios: A parent component passes a callback function to a child component. React does not recommend this method. Use a useReducer hook and send a dispatch to avoid this method.
- Example:
import React, { useEffect, useState, useCallback } from 'react'; Function Son({callback}) {renturn (<a onClick={()=>callback(" callback ")}> function Parent() { Const [name,setName] = useState("") useEffect(() => {console.log(" ") setName(" ")},[]); const callback = useCallback(name => { setName(name); } []); return ( <> <Son callback={callback} />; name:{name} <> ) }Copy the code
UseMemo (Key Grasp)
UseCallback (fn, deps) is equivalent to useMemo(() => FN, deps).
You pass in the create function and the dependency array as arguments to useMemo, which recalculates memoized values only when a dependency changes. This optimization helps avoid costly calculations every time you render.
If the dependency array is not provided, useMemo evaluates the new value each time it renders.
You can use useMemo as a means of performance optimization, but don’t take it as a semantic guarantee!
Application Scenarios:
-
Store an expensive calculation
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
-
Skip an expensive re-rendering of child nodes
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
Copy the code
UseRef (important)
UseRef returns a mutable REF object whose current property is initialized as the passed parameter (initialValue). The ref object returned remains constant throughout the life of the component.
const refContainer = useRef(initialValue);
Copy the code
Usage Scenarios:
-
Access the child component DOM
function TextInputWithFocusButton() { const inputEl = useRef(null); Const onButtonClick = () => {// 'current' points to the text input element inputel.current.focus () mounted to the DOM; }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }Copy the code
- Save instance variables
function Timer() { const intervalRef = useRef(); useEffect(() => { const id = setInterval(() => { // ... }); intervalRef.current = id; return () => { clearInterval(intervalRef.current); }; }); / /... Return <div> uses useRef to store instance variables </div>}Copy the code
UseImperativeHandle (not commonly used)
useImperativeHandle(ref, createHandle, [deps])
Copy the code
UseImperativeHandle allows you to customize the instance value exposed to the parent component when using a ref. In most cases, imperative code like ref should be avoided. UseImperativeHandle should be used with the forwardRef:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
Copy the code
In this case, the parent component rendering
UseLayoutEffect (uncommon)
Its function has the same signature as useEffect and is used in the same way, but it calls Effect synchronously after all DOM changes. You can use it to read DOM layouts and trigger rerenders synchronously. The update schedule inside useLayoutEffect is refreshed synchronously before the browser performs the drawing.
Use standard Useeffects whenever possible to avoid blocking visual updates.
- UseEffect differs from componentDidMount and componentDidUpdate in that the function passed to useEffect is delayed after the browser has finished laying out and drawing.
- UseLayoutEffect is called at the same time as componentDidMount and componentDidUpdate.
UseDebugValue (not commonly used)
For specific usage, refer to the official documentation
The differences between useMemo and useCallback and their application scenarios
Both useMemo and useCallback receive the same parameters. The first parameter is the callback and the second parameter is the data to depend on
1. Recalculate the result only when the data changes, that is, it serves as a cache.
Differences between the two:
1. The useMemo calculation result is the value returned by the return and is mainly used to cache the calculation result value. The application scenario is as follows: Status to be calculated \
2. The calculation result of useCallback is a function, which is mainly used for caching functions. The application scenarios are as follows: Functions that need to be cached because functional components are rerefreshed every time there is a change in state. Some functions need to be cached to improve performance and reduce resource waste.
The hooks into the order
Custom Hooks
By custom Hook, the reusable logic of multiple components can be extracted to realize logic reuse.
Examples (the following examples are from Ruan Yifeng’s weblog) :
const Person = ({ personId }) => { const [loading, setLoading] = useState(true); const [person, setPerson] = useState({}); useEffect(() => { setLoading(true); fetch(`https://swapi.co/api/people/${personId}/`) .then(response => response.json()) .then(data => { setPerson(data); setLoading(false); }); }, [personId]) if (loading === true) { return <p>Loading ... </p> } return <div> <p>You're viewing: {person.name}</p> <p>Height: {person.height}</p> <p>Mass: {person.mass}</p> </div> }Copy the code
We’ve taken the logic to get person out of the above code to make it easier for other similar component calls
const usePerson = (personId) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch(`https://swapi.co/api/people/${personId}/`)
.then(response => response.json())
.then(data => {
setPerson(data);
setLoading(false);
});
}, [personId]);
return [loading, person];
};
Copy the code
UsePerson in the above code is a custom hook that we can use in the rest of the components:
const Person = ({ personId }) => { const [loading, person] = usePerson(personId); if (loading === true) { return <p>Loading ... </p>; } return ( <div> <p>You're viewing: {person.name}</p> <p>Height: {person.height}</p> <p>Mass: {person.mass}</p> </div> ); };Copy the code
Implement several commonly used custom hooks yourself
- UseFetch (simple version): Fetch interface data
import { useState, useEffect} from 'react';
import fetch from 'fetch';
/**
* @param {String} url
* @param {Object} initState
*/
const useFetch_0 = (url, initState) => {
const [isLoading, setIsLoading] = useState(false);
const [data, setDate] = useState(initState);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () =>{
setIsLoading(true);
try {
const res = await fetch(url);
setDate(res);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
}
fetchData();
}, [url]);
return [
data,
isLoading,
isError,
];
}
export default useFetch_0;
Copy the code
Const [data,isLoading,isError] = useFetch(url,initState)
- UsePrevious: Obtains the props and state of the previous round
function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; } function Counter() {const [count, setCount] = useState(0); const prevCount = usePrevious(count); return <h1>Now: {count}, before: {prevCount}</h1>; }Copy the code
Third-party quality custom Hooks
Github currently has a number of excellent custom hooks: github.com/rehooks/awe…
Custom hooks example
useDeepCompareEffect
import React from 'react';
import { useDeepCompareEffect } from 'use-deep-compare';
function App({ object, array }) {
useDeepCompareEffect(() => {
// do something significant here
return () => {
// return to clean up that significant thing
};
}, [object, array]);
return <div>{/* render significant thing */}</div>;
}
Copy the code
useDeepCompareCallback
import React from 'react';
import { useDeepCompareCallback } from 'use-deep-compare';
function App({ object, array }) {
const callback = useDeepCompareCallback(() => {
// do something significant here
}, [object, array]);
return <div>{/* render significant thing */}</div>;
}
Copy the code
useDeepCompareMemo
import React from 'react';
import { useDeepCompareMemo } from 'use-deep-compare';
function App({ object, array }) {
const memoized = useDeepCompareMemo(() => {
// do something significant here
}, [object, array]);
return <div>{/* render significant thing */}</div>;
}
Copy the code
-
use-debounce
import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';
export default function Input() {
const [text, setText] = useState('Hello');
const [value] = useDebounce(text, 1000);
return (
<div>
<input
defaultValue={'Hello'}
onChange={(e) => {
setText(e.target.value);
}}
/>
<p>Actual value: {text}</p>
<p>Debounce value: {value}</p>
</div>
);
}
Copy the code
-
use-async-memo
const data = useAsyncMemo(doAPIRequest, [])
Implement the common lifecycle of a Class component using Hooks
-
componentDidMount
useEffect(()=>{ // do something },[])
-
componentDidUpdate
useEffect(()=>{ // do something })
-
componentWillUnmount
useEffect(()=>{ return ()=> { // do something } },[])
-
getDerivedStateFromProps:The official tutorial
function ScrollView({row}) { let [isScrollingDown, setIsScrollingDown] = useState(false); let [prevRow, setPrevRow] = useState(null); if (row ! PrevRow == prevRow) {// Row has changed since last render. Update the isScrollingDown. setIsScrollingDown(prevRow ! == null && row > prevRow); setPrevRow(row); } return `Scrolling down: ${isScrollingDown}`; }Copy the code
- shouldComponentUpdate
You can use useMemo, but memo is recommended if you are not involved in comparing internal states of components
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
Copy the code
Hooks FAQ
Most of the common problems are covered in the above code, and for the rest, please refer to the official documentation problems module
Keep in mind that
- Use hooks only at the top level
- Only the React function calls the Hook
- Please refer to the official documentation hooks Rules for detailed rules
conclusion
- UseState and useEffect can cover most service scenarios
- Complex components use useReducer instead of useState
- Use useContext, useRef, or third-party custom hooks when useState and useEffect do not meet business requirements
- UseMemo and useCallback are used for performance optimization, and the code should work without them