React Hooks 【 nearly 1W words 】+ project combat
We all know that the core idea of React is to split a page into a bunch of independent, reusable components and connect them together in a top-down one-way data flow. But if you use React on large work projects, you’ll find that many react components in your projects are actually lengthy and hard to reuse. Components written as class, in particular, contain their own state, making it cumbersome to reuse such components. Hence the react-hooks.
What are react-hooks
-
React has always advocated using function components, but sometimes you have to use class components when you need to use other functions such as state and life cycles. Function components have no instances and no life cycles, so you can only use class components.
-
React hooks are new since Act 16.8. They allow you to use state and other React features without writing classes.
-
React Hooks include custom Hooks, which begin with use.
React-hooks and class components
1. Deficiencies of class components
-
State logic is hard to reuse
- Reusing state logic between components is difficult and may require it
render props
(Render properties) orHOC
(higher-order components), but both render attributes and higher-order components wrap a parent container (typically div elements) around the original component, resulting in hierarchical redundancy.
- Reusing state logic between components is difficult and may require it
-
Tends to be complex and difficult to maintain
-
Mixing up irrelevant logic in life cycle functions (registering events and other logic in componentDidMount, uninstalling events in componentWillUnmount, etc.) makes it easy to write bugs separately.
-
Class components have access to and processing of state all over the place, making components difficult to break down into smaller components.
-
-
This points to the problem
-
When a parent passes a function to a child, it must bind this.
-
React components bind this methods differently
class App extends React.Component<any, any> { handleClick2; constructor(props) { super(props); this.state = { num: 1, title: 'react' }; this.handleClick2 = this.handleClick1.bind(this); } handleClick1() { this.setState({ num: this.state.num + 1 }); } handleClick3 = () => { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <button onClick={this.handleClick2}>btn1</button> <button onClick={this.handleClick1.bind(this)}>btn2</button> <button onClick={() => this.handleClick1()}>btn3</button> <button onClick={this.handleClick3}>btn4</button> </div> ) } } Copy the code
-
Prerequisite: The child component has internal performance optimizations, such as (react.pureComponent)
-
The first is to bind this in the constructor: then every time the parent component refreshes, if the other props passed to the child component stays the same, the child component does not refresh.
-
The second option is to bind this in the render() function: because bind returns a new function, a new function is generated each time the parent component refreshes, even if the parent component passes the other props to the child component, and the child component refreshes each time.
-
The third way is to use arrow functions: when the parent component refreshes, even if the two arrow functions have the same body, a new arrow function is generated, so the child component refreshes every time.
-
The fourth way is to use the static properties of the class: the principle is similar to the first method, but more concise.
2. The react – hooks
-
Can optimize three major problems for class components
-
Reusing state logic without modifying component structure (custom Hooks)
-
Ability to break down interrelated parts of a component into smaller functions (such as setting up subscriptions or requesting data)
-
Separation of concerns for side effects: Side effects are the logic that does not occur during the data-to-view transformation, such as Ajax requests, accessing native DOM elements, local persistent caching, binding/unbinding events, adding subscriptions, setting timers, logging, etc. In the past, these side effects were written in the class component lifecycle functions. UseEffect is executed after the rendering is complete, useLayoutEffect is executed after the browser layout and before the painting.
Three, notes
-
Call a Hook only from the outermost layer inside a function, not from loops, conditional judgments, or child functions.
-
You can only call a Hook in the React function component, not in other JavaScript functions.
React-hooks; react-hooks
-
useState
-
useCallback
-
useEffect
-
useMemo
-
useContext
-
useLayoutEffect
-
useRef
-
useReducer
Introduction to Hooks functions
1. UseState stores data and sends updates
-
React assumes that when you call useState multiple times, you can guarantee that the order of their calls will remain the same each time you render.
-
Use useState to add some internal state to the useState function component. React retains this state when the component is updated or the component is repeatedly rendered.
-
The only argument to useState is the initial state.
-
UseState returns an array: a state, and a function to update the state.
-
During initial rendering, the state returned is the same as the value of the first parameter passed in
-
You can call this function from an event handler or some other place. It is similar to the this.setState of the class component, but it does not merge the new state with the old state, instead replacing it directly.
-
-
State in useState is updated, and the entire function component is executed again from beginning to end. It needs to coordinate with useEffect, useMemo, useCallback and other hooks to optimize component performance.
1.1 useState example
import React, { useState } from "react";
import ReactDOM from "react-dom";
interface ChildInt {
handleClick: Function,
}
interface ChildOInter extends ChildInt {
num: number,
}
interface ChildTInter extends ChildInt {
text: number,
}
const Child1 = (props: ChildOInter) => {
console.log(props);
const { num, handleClick } = props;
return (
<div
onClick={() => {
handleClick(num + 1);
}}
>
child
<span>{ num }</span>
</div>
)
}
const Child2 = (props: ChildTInter) => {
const { text, handleClick } = props;
return (
<div>
child2
<Grandson text={text} handleClick={handleClick} />
</div>
)
}
const Grandson = (props: ChildTInter) => {
const { text, handleClick } = props;
return (
<div
onClick={() => {
handleClick(text+1);
}}
>
grandson
{ text }
</div>
)
}
const Parent = () => {
let [ num, setNum ] = useState(0);
let [ text, setText ] = useState(1);
return (
<div>
<Child1 num={num} handleClick={setNum} />
<Child2 text={text} handleClick={setText} />
</div>
)
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
Copy the code
1.2 Each render is a separate closure
-
Each time has its own props and state
-
Each render has its own event handler
-
When the status update is clicked, the function component is called again, each rendering is independent and the value taken is not affected by subsequent operations.
function Counter(){ let [number,setNumber] = useState(0); Function alertNumber(){setTimeout()=>{// alert(number); }, 3000); } return ( <> <p>{number}</p> <button onClick={()=>setNumber(number+1)}>+</button> <button onClick={alertNumber}>alertNumber</button> </> ) }Copy the code
1.3 Functional update
- If the new state needs to be computed using the previous state, the callback function can be passed as an argument to setState. The callback will accept the previous state and return an updated value.
function Counter(){ let [number,setNumber] = useState(0); function lazy(){ setTimeout(() => { // setNumber(number+1); State setNumber(number=>number+1); // State setNumber(number=>number+1); }, 3000); } return ( <> <p>{number}</p> <button onClick={()=>setNumber(number+1)}>+</button> <button onClick={lazy}>lazy</button> } < / a >)Copy the code
1.4 Lazy initialization of state
-
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 obtained through complex calculations, you can pass in a function that computes and returns the initial state, which is called only during the initial render.
1.5 Performance Optimization
1.5.1 Object.is (Shallow comparison)
-
The Hook uses object. is internally to compare whether the new/old state is equal (object. is and === are distinguished by +0 and -0, NaN).
-
Unlike the setState method in the Class component, if you modify the state without changing the value of the passed state, you do not re-render.
-
Unlike the setState method in the Class component, useState does not automatically merge update objects. You can merge updated objects using a functional setState in conjunction with the expansion operator.
Function Counter(){const [Counter,setCounter] = useState({name:' Counter ',number:0}); Console. log('render Counter') // If the state is not changed when you modify it, Return (<> <p>{counter. Name}:{counter. Number}</p> <button onClick={()=>setCounter({... counter,number:counter.number+1})}>+</button> <button onClick={()=>setCounter(counter)}>++</button> </> ) }Copy the code
1.5.2 Reduce the number of renders
-
By default, whenever the parent component state changes (regardless of whether the component depends on that state), the child component is also rerendered.
-
General optimizations:
-
Class component: You can use PureComponent
-
Function components: Use react. memo. After passing a function component to the Memo, a new component is returned. What the new component does: It does not re-render if the received property value has not changed.
-
Const [number,setNumber] = useState(0), which means a new value is generated each time (even if the value has not changed). Even if react.Memo is used, it will still be rerendered.
import React, { memo, useState } from 'react'; interface childProps { number: number, onClick: () => void, } const Child: React.FC<childProps> = memo(({ number, onClick }) => { console.log('SubCounter render'); return ( <button onClick={onClick}>{number}</button> ) }); const Counter: React.FC = () => { console.log('Counter render'); Const [name, setName]= useState<string>(' counter '); const [ number, setNumber ] = useState<number>(0); const data = number; const addClick = () =>{ setNumber(number+1); }; return ( <> <input type="text" value={name} onChange={(e)=>setName(e.target.value)} /> <Child number={data} onClick={addClick}/> </> ) } export default Counter;Copy the code
-
-
Further optimization
- Each time the parent component is updated, the child component is rerendered even if the value passed in to the child component does not change. So you need to use
useCallback
anduseMemo
Two hooks to cache variables or functions to be passed to the child so that it does not re-render the child without changes.
import React, { memo, useState, useMemo, useCallback } from 'react'; interface childProps { number: number, onClick: () => void, } const Child: React.FC<childProps> = memo(({ number, onClick }) => { console.log('SubCounter render'); return ( <button onClick={onClick}>{number}</button> ) }); const Counter: React.FC = () => { console.log('Counter render'); Const [name, setName]= useState<string>(' counter '); const [ number, setNumber ] = useState<number>(0); // When the parent component is updated, the variables and functions are re-created each time, so the attributes received by the child component are considered new each time, so the child component is updated accordingly. Const data = useMemo(() => number, [number]); const data = useMemo(() => number, [number]); Const addClick = useCallback(() =>{setNumber(number+1); const addClick = useCallback(() =>{setNumber(number+1); }, [number]); return ( <> <input type="text" value={name} onChange={(e)=>setName(e.target.value)} /> <Child number={data} onClick={addClick}/> </> ) } export default Counter;Copy the code
- Each time the parent component is updated, the child component is rerendered even if the value passed in to the child component does not change. So you need to use
2. The useEffect component updates the side effect hook
-
Effect: Logic that does not occur during the data-to-view transformation, such as Ajax requests, accessing native DOM elements, local persistent caching, binding/unbinding events, adding subscriptions, setting timers, logging, etc.
-
Side effects can be divided into two categories: those that need to be removed and those that do not.
-
Changing the DOM, sending Ajax requests, and performing other side effects inside function components (in this case, during the React rendering phase) were previously not allowed, as they could cause unexplained bugs and break UI consistency.
-
UseEffect is an Effect Hook that gives function components the ability to manipulate side effects. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in the Class component, but has been consolidated into an API.
-
UseEffect receives a function that is executed after the component has been rendered to the screen, with the requirement that it either returns a function that clears side effects or nothing at all.
-
To prevent the function from triggering the useEffect callback with every update, you need to qualify it with a second argument (an array). Multiple dependencies can be passed into the array, and useEffect will be re-executed if any dependency changes. Executing the callback on the first rendering is equivalent to executing componentDidMount in the Class component. The dependency changes and useEffect is executed again, where useEffect is equivalent to componentDidUpdate in the class component.
-
The useEffect callback returns a function equivalent to componentWillUnmount in the class component.
-
Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser update screen, making your application seem more responsive. In most cases, effects do not need to be executed synchronously. In individual cases (such as measuring layout), there is a separate useLayoutEffect Hook for you to use, with the same API as useEffect.
/ / function getUserInfo(a){return new Promise((resolve)=>{setTimeout(()=>{resolve({name:a, age:16,)) }) },500) }) } const Demo = ({ a }) => { const [ userMessage, setUserMessage ] = useState({}); const div = useRef(); const [number, setNumber] = useState(0); Const handleResize = () => {} /* useEffect Get into an infinite loop */ useEffect(()=>{/* Request data */ getUserInfo(a). Then (res=>{setUserMessage(res)}) /* Manipulate dom */ Console. log(div.current) /* div */ /* timer timer etc */ const timer = setInterval(() => console.log(666), 1000); /* Event listener etc. */ window.addEventListener('resize', handleResize); /* The useEffect function is reexecuted only if props->a and state->number are changed. Return () => {clearInterval(timer); return () => {clearInterval(timer); window.removeEventListener('resize', handleResize); } },[ a, number ]) return ( <div ref={div} > <span>{ userMessage.name }</span> <span>{ userMessage.age }</span> <div onClick={ ()=> setNumber(1) } >{ number }</div> </div> ) }Copy the code
UseEffect is a syntax sugar that cannot be used directly with async await.
*/ useEffect(async() => {/* request data */ const res = await getUserInfo(payload); }, [ a, number ])Copy the code
2.1 Use the Class component to modify the title
-
In this class, we need to write duplicate code in both lifecycle functions because, in many cases, we want to do the same thing when a component loads and updates. We expect it to be executed after each render, but the React class component does not provide such a method. Even if we extract a method, we still have to call it in two places. UseEffect is executed after the first rendering and after every update.
class Counter extends React.Component { state = { number:0 }; add = () => { this.setState({ number:this.state.number+1 }); }; componentDidMount() { this.changeTitle(); } componentDidUpdate() { this.changeTitle(); } changeTitle = () => {document.title = 'you have clicked ${this.state.number} times'; }; render() { return ( <> <p>{this.state.number}</p> <button onClick={this.add}>+</button> </> ) } }Copy the code
2.2 useEffect to modify the title
- Every time we re-render, a new effect is generated, replacing the previous one. In a sense, an effect is more like a part of a render result — each effect belongs to a specific render.
import React, {Component,useState,useEffect} from 'react'; import ReactDOM from 'react-dom'; function Counter() { const [number,setNumber] = useState(0); UseEffect is a function that executes after the first rendering and after the update is complete. UseEffect (() => {document.title = 'you clicked ${number} times'; }); return ( <> <p>{number}</p> <button onClick={()=>setNumber(number+1)}>+</button> </> ) } ReactDOM.render(<Counter />, document.getElementById('root'));Copy the code
3.3 Side effects of removal
- The side effect function can also specify how to clean the side effect by returning a function that is executed before the component is unloaded to prevent memory leaks. If the component is rendered multiple times, the previous effect is cleared before the next effect is executed.
function Counter(){ let [ number,setNumber ] = useState(0); let [ text,setText ] = useState(''); ComponentDidUpdate useEffect(()=> {console.log(' start a new timer ') let $timer = setInterval(()=>{ setNumber(number=>number+1); }, 1000); // useEffect If a function is returned, it will be called when the component is unloaded or updated. /* return () => {console.log('destroy effect'); clearInterval($timer); * /}}); / /} []); // Either pass an empty dependency array here, Return (<> <input value={text} onChange={(event)=>setText(event.target.value)}/> <p>{number}</p> <button>+</button> </> ) }Copy the code
2.4 Skip effect for performance optimization
-
An array of dependencies controls the execution of useEffect
-
If certain values don’t change between rerenderers, you can tell React to skip the effect call by passing an array as the second optional argument to useEffect.
-
If you want to execute effect once (only when the component is mounted and unmounted), you can pass an empty array ([]) as the second argument. This tells React that your effect doesn’t depend on any value in props or state, so it never needs to be repeated.
-
It is recommended to enable the Strict-deps rule in eslint-plugin-react-hooks. This rule warns when you add false dependencies and suggests how to fix them.
function Counter(){ let [number,setNumber] = useState(0); let [text,setText] = useState(''); UseEffect (()=>{console.log('useEffect'); let $timer = setInterval(()=>{ setNumber(number=>number+1); }, 1000); },[text]); // The array represents the effect dependent variable, Efffect return (<> <input value={text} onChange={(event)=>setText(event.target.value)}/> <p>{number}</p> <button>+</button> </> ) }Copy the code
2.5 Separation of concerns using multiple effects
- 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.
// Class FriendStatusWithCounter extends React.Com {constructor(props) {super(props); this.state = { count: 0, isOnline: null }; this.handleStatusChange = this.handleStatusChange.bind(this); } componentDidMount() { document.title = `You clicked ${this.state.count} times`; ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } / /... }Copy the code
-
You can see how the logic for setting document.title is split between componentDidMount and componentDidUpdate, And how the subscription logic is split between componentDidMount and componentWillUnmount. And componentDidMount contains code for two different functions at once. This would confuse the life cycle function.
-
Hooks allow us to separate code according to its purpose, rather than life cycle functions. React calls each effect in the component in the order in which it was declared.
Function FriendStatusWithCounter(props) {const [count, setCount] = useState(0); // Hooks function FriendStatusWithCounter(props) {const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); / /... }Copy the code
3. UseLayoutEffect render useEffect before update
-
UseEffect is not executed until all renderings are complete.
-
UseLayoutEffect is executed after the browser layout and before the painting.
-
Its function signature is the same as useEffect, but it calls Effect synchronously after all DOM changes.
-
You can use it to read DOM layouts and trigger rerenders synchronously.
-
The update schedule within useLayoutEffect is synchronized to refresh before the browser executes the drawing.
-
Use the standard useEffect whenever possible to avoid blocking view updates.
-
Component update mount complete -> Execute useLayoutEffect callback -> Browser DOM drawing complete -> Execute useEffect callback.
function LayoutEffect() { const [color, setColor] = useState('red'); useLayoutEffect(() => { alert(color); }); useEffect(() => { console.log('color', color); }); return ( <> <div id="myDiv" style={{ background: Color color}} > < / div > < button onClick = {() = > setColor () 'red'} > red < / button > < button onClick = {() = > Yellow setColor (' yellow ')} > < / button > < button onClick = {() = > setColor (' blue ')} > blue < / button > < / a >). }Copy the code
const DemoUseLayoutEffect = () => { const target = useRef(); UseLayoutEffect (() => {/* We need to draw the dom before, */ const {x,y} = getPositon() */ animate(target.current,{x,y}); } []); return ( <div > <span ref={ target } className="animate"></span> </div> ) }Copy the code
4. UseRef retrieves elements and caches data
4.1 useRef
-
CreateRef is used for class components and React elements, and useRef is used for function components.
-
UseRef returns a mutable REF object whose current property is initialized as the passed parameter (initialValue).
const refContainer = useRef(initialValue);
Copy the code
- The ref object returned by useRef remains the same throughout the life of the component, meaning that the same ref object is returned each time the function component is re-rendered (using react.createref, refs are recreated each time the component is re-rendered).
import React, { useState, useEffect, useRef } from 'react'; import ReactDOM from 'react-dom'; function Parent() { let [number, setNumber] = useState(0); return ( <> <Child /> <button onClick={() => setNumber({ number: number + 1 })}>+</button> </> ) } let input; function Child() { const inputRef = useRef(); console.log('input===inputRef', input === inputRef); input = inputRef; function getFocus() { inputRef.current.focus(); <> <input type="text" ref={inputRef} /> <button onClick={getFocus}> </button> </> ReactDOM.render(<Parent />, document.getElementById('root'));Copy the code
4.2 Advanced use of useRefCache data
-
Usestate, usereducers can save the current data source, but if they update the data source’s function execution will inevitably bring the entire component to the rendering.
-
If you declare variables inside a function component, the next update will reset, so useRef is a good choice if you want to keep the data quietly without triggering a function update.
const currenRef = useRef(InitialData)
Copy the code
The first parameter of useRef can be used to initialize save data, which can be obtained on the current property, or we can assign a new data source to the current property. Currenref. current = newValue.
- Let’s take a look at the clever use of useRef using the React-redux source code
React-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks Its source code makes extensive use of useMemo to do data judgment.
/* None of the useRef used here is bound to a DOM element, */ * react-redux props = useRef() // const lastWrapperProps = useRef( Const lastWrapperProps = useRef(wrapperProps) // Whether the props are being updated const renderIsScheduled = useRef(false)Copy the code
React-redux caches data using useRef.
Function captureWrapperProps(lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, ActualChildProps childPropsFromStoreUpdate, notifyNestedSubs) {/ / we want to capture packaging props and props, Current = wrapperProps // subprops lastChildProps. Current = actualChildProps // Merge childprops After the formation of the prop renderIsScheduled. Current = false}Copy the code
As can be seen from the above, react-Redux uses the method of reassignment to change the cache data source to avoid unnecessary data updates. If useState is used to store data, the component will inevitably be re-rendered, so useRef is used to solve this problem.
4.3 forwardRef
- Because a function component has no instance, it cannot receive a REF attribute like a class component.
Function Parent() {return (<> <Child ref={XXX} /> <Child /> <button>+</button> </>)}Copy the code
-
The parent can manipulate the parent’s ref object.
-
The forwardRef forwards the REF object in the parent component to the DOM element in the child component.
-
The child component accepts props and ref as parameters.
function Child(props,ref){ return ( <input type="text" ref={ref}/> ) } Child = React.forwardRef(Child); function Parent(){ let [number,setNumber] = useState(0); // When using class components, creating ref returns an object whose current property value is null // Only has a value if it is assigned to an element's REF property // So the parent component (class component) creates a ref object and passes it to the child component (class component), <Child ref={XXX} /> <Child ref={XXX} / inputRef = useRef(); // {current:''} function getFocus(){ inputRef.current.value = 'focus'; inputRef.current.focus(); } return ( <> <Child ref={inputRef}/> <button onClick={()=>setNumber({number:number+1})}>+</button> <button OnClick ={getFocus}> </button> </>)}Copy the code
4.4 useImperativeHandle
-
UseImperativeHandle allows you to customize the instance value exposed to the parent component when using a ref, rather than letting the parent component do what it wants.
-
In most cases, imperative code like ref should be avoided. UseImperativeHandle should be used together with the forwardRef.
-
A parent component can use more than one REF in an action child component.
import React,{useState,useEffect,createRef,useRef,forwardRef,useImperativeHandle} from 'react'; Function Child(props,parentRef){// Set focusRef = useRef(); let inputRef = useRef(); UseImperativeHandle (parentRef, () => (// This function returns an object that serves as the value of the parent component's current property // in this way, The parent component can use multiple ref return {focusRef, inputRef, name:' counter ', focus(){focusref.current.focus (); }, changeText(text) { inputRef.current.value = text; }}}); return ( <> <input ref={focusRef}/> <input ref={inputRef}/> </> ) } Child = forwardRef(Child); function Parent(){ const parentRef = useRef(); // {current:''} function getFocus(){ parentRef.current.focus(); / / this property because the child is not defined in the component, implements the protection, so the code is invalid parentRef. Current. AddNumber (666); parentRef.current.changeText('<script>alert(1)</script>'); console.log(parentRef.current.name); } return (<> <ForwardChild ref={parentRef} /> <button onClick={getFocus}>)}Copy the code
5. UseContext freely retrieves the context
-
Receives a context object (the return value of React. CreateContext) to retrieve the context value passed by the parent component.
-
The current context value is determined by the < MyContext.provider > value prop of the upper-layer component closest to the current component.
-
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 context for the lower component.
-
The current value is the most recent parent group. The useContext parameter is usually introduced by createContext or passed by the parent context (context). UseContext can be used instead of context.Consumer to retrieve the value stored in the Provider.
Another important point about Context is that when the Context Provider’s value changes, all its child consumers rerender.
import React, { useContext } from 'react';
const ThemeContext = React.createContext('light');
const ReactContext = () => {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
)
};
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
)
};
function ThemedButton(props) {
const theme = useContext('ThemeContext');
return <Button theme={theme} />
}
export default ReactContext;
Copy the code
import React,{useState,memo,useMemo,useCallback,useReducer,createContext,useContext} from 'react'; import ReactDOM from 'react-dom'; const initialState = 0; function reducer(state=initialState,action){ switch(action.type){ case 'ADD': return {number:state.number+1}; default: break; } } const CounterContext = createContext(); // The first method to get CounterContext is: Hook function SubCounter_one(){return (< countercontext.consumer >{value =>(<> <p>{value.state.number}</p> <button OnClick ={()=>value.dispatch({type:'ADD'})}>+</button> </>)} </ CounterContext.consumer >) Function SubCounter(){const {state, dispatch} = useContext(CounterContext); return ( <> <p>{state.number}</p> <button onClick={()=>dispatch({type:'ADD'})}>+</button> </> ) } /* class SubCounter extends React.Component{ static contextTypes = CounterContext this.context = {state, dispatch} } */ function Counter(){ const [state, dispatch] = useReducer((reducer), initialState, ()=>({number:initialState})); return ( <CounterContext.Provider value={{state, dispatch}}> <SubCounter/> </CounterContext.Provider> ) } ReactDOM.render(<Counter />, document.getElementById('root'));Copy the code
6. Redux in the useReducer stateless component
-
Redux can take advantage of complex logic, and dispatch can be enhanced through middleware. Redux-thunk, redux-sage, redux-Action, and redux-Promise are all good middleware that can turn synchronous reducer into asynchronous reducer.
-
UseReducer is a Redux-like API provided by React-hooks that can run in stateless components.
-
UseState is internally implemented by the useReducer.
-
UseState alternative, which 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.
-
The first parameter accepted by the useReducer is a function, which can be considered as a reducer. The reducer parameters are the state and action in the normal Reducer, and the changed state is returned. The useReducer returns an array with the initial value of state as the second argument. The first element of the array is the value of state after the update. The second argument is the dispatch function that dispatches the update.
-
A dispatch trigger triggers component updates. The update function in useState (which returns the second argument) and the update function in useReducer (which returns the second argument) can rerender components.
import React, { useReducer, useCallback } from 'react'; const MyChildren = ({ dispatch: dispatchNumber, number }) => { console.log('number', number); const dispatchFn = useCallback(() => { dispatchNumber({ name: 'add' }); }, [dispatchNumber]); Return (<div> <button onClick={dispatchFn}> child button </button> {number} </div>)} const DemoUseReducer = () => {const [ number, dispatchNumber ] = useReducer((state, action) => { const { payload, name } = action; console.log(state); switch(name) { case 'add': return state + 1; case 'sub': return state - 1; case 'reset': return payload; default: return; }}, 0); return ( <div> { number } <button onClick={() => dispatchNumber({ name: </button> <button onClick={() => dispatchNumber({name: </button> <button onClick={() => dispatchNumber({name: 'reset', payload: 666})}> assignment </button> <MyChildren dispatch={dispatchNumber} number={number} /> </div>)} export default DemoUseReducer;Copy the code
let initialState = 0; // if you want the initialState to be a {number:0} // you can pass a function like this in the third argument () => ({number:initialState}) // this function is an lazy initialization function that can be used for complex calculations, Then return the final initialState const [state, dispatch] = useReducer(Reducer, initialState, init); const initialState = 0; function reducer (state, action) { switch (action.type) { case 'increment': return { number: state.number + 1 }; case 'decrement': return { number: state.number - 1 }; default: throw new Error(); } } function init(initialState) { return { number:initialState }; } function Counter(){ const [ state, dispatch ] = useReducer(reducer, initialState, init); return ( <> Count: {state.number} <button onClick={() => dispatch({ type: 'increment' })}> + </button> <button onClick={() => dispatch({ type: 'decrement' })}> - </button> </> ) }Copy the code
Of course, actual business logic may be more complicated, and we need to do more complicated logical operations in reducer.
7. UseMemo performance optimization
-
The advantage of useMemo is that it can form a separate rendering space, allowing components and variables to be updated according to agreed rules.
-
Render conditions depend on the second parameter, DEps.
-
We know that updates to stateless components are updates from start to finish, and if you want to re-render a part of the view instead of the entire component, using useMemo is the best way to avoid unwanted updates and unnecessary context execution.
-
Note: the memo componentShouldUpdate is the same as the stateless component’s componentShouldUpdate. UseMemo is a smaller componentShouldUpdate element.
-
The memo uses a combination of pureComponent and componentShouldUpdate to perform a shallow comparison of the props passed in, and then determine which props need to be updated based on the value returned by the second function.
*/ import React, {useEffect} from 'React '; const memoComp = ({ goodList, dispatch }) => { useEffect(() => { dispatch({ name: 'goodList', }); }, [dispatch]); Return <Select placeholder={' please Select placeholder '} style={{width: 400, marginRight: 10 }} onChange={(value) => setSeivceId(value) } > { goodList.map((item, index) => <Option key={index + 'asd' + item.itemId} value={item.itemId} > {item.itemName} </Option>) } </Select> }; /* Determine whether the previous goodList is equal to the new goodList. If so, do not update this component so that you can make your own rendering convention. */ const DemoMemo = connect(state => ({goodList: state.goodList }))(memo(memoComp, (pre, next) => is(pre.goodList, next.goodList)))Copy the code
UseMemo is used in much the same way as memo, to determine whether a useMemo callback function is executed based on whether the current criteria are met. The second parameter of useMemo is a DEPS array. The parameters in the array determine whether useMemo updates the callback function or not. The return value of useMemo is the result of the decision update. It can be applied to elements, it can be applied to components, it can be applied to context. If you have a looping list element, then useMemo is a good choice. Let’s explore the advantages of useMemo.
/* Usememo-wrapped lists can be restricted to updating the list if and only if the list changes, */ const dataList = useMemo(() => (<div>{selectList.map(I, v) => ( <span className={style.listSpan} key={v} > {i.patentName} </span> ))} </div> ), [selectList]);Copy the code
7.1. UseMemo can reduce unnecessary loops and reduce unnecessary rendering
UseMemo (() => (<Modal width={'70%'} visible={listshow} footer={[<Button key="back" > Cancel </Button>, <Button key="submit" type="primary" > </Button>]} > selectList={selectList} cacheSelectList={cacheSelectList} setCacheSelectList={setCacheSelectList} /> </Modal> ), [listshow, cacheSelectList])Copy the code
7.2 useMemo can reduce the number of times subcomponents are rendered
Const DemoUseMemo = () => {/* The useMemo wrapped log function avoids redeclaration every time a component is updated, */ const newLog = useMemo(()=>{const log = ()=>{console.log(6666)} return log},[]) return <div onClick={ () => newLog() } ></div> }Copy the code
7.3 useMemo makes functions run only when a dependency changes, which avoids a lot of unnecessary overheadIf the context wrapped by useMemo forms a separate closure, the previous state value will be cached, and the updated state value will not be retrieved without the associated update condition
.
const DemoUseMemo=()=>{ const [ number ,setNumber ] = useState(0); Const newLog = useMemo(() => {const log = () => {/* The number printed after the span is not updated */ console.log(number); } return log; /* [] no number */}, []) return <div> <div onClick={() => newLog()}> print </div> <span onClick={() => setNumber(number + 1)}> add </span> </div> }Copy the code
8. UseCallback Callback function of the useMemo version
-
Both useMemo and useCallback receive the same parameters and are executed after their dependencies change.
-
The difference is that useMemo returns the result of a function run, which can be a value, a function, a list, etc. UseCallback returns a function.
-
When the parent component passes parameters to the child component, the stateless component regenerates the props function each time, so that the function passed to the child component changes each time, triggering the update of the child component. Some updates are unnecessary, so we can handle this function via usecallback and pass it as props to child components.
import React, { useState, useEffect, useCallback } from 'react'; Const DemoChildren = react.memo ((props)=>{/* Only sub updates are printed when initialized */ console.log(' sub updates ') useEffect(()=>{ Props. GetInfo (' subcomponent ')},[]) return <div> subcomponent </div>}) const DemoUseMemo=({id})=>{const [number, SetNumber] = useState(1) /* The first argument to usecallBack (sonName)=>{console.log(sonName)} is processed to getInfo */ const getInfo = UseCallback ((sonName)=>{console.log(sonName)},[id]) return <div> {/* Click the button to trigger the parent component update, <button onClick={()=>setNumber(number+1)} > added </button> <DemoChildren getInfo={getInfo} /> <div>{number}</div> </div> } export default DemoUseMemo;Copy the code
It should be noted that useCallback must work with the React. Memo and pureComponent, otherwise performance may deteriorate rather than improve.
Vi. Custom Hook
-
Custom hooks are more of a convention than a feature. If the function name starts with use and calls other hooks, it is called a custom Hook.
-
Sometimes we want to reuse some state logic between components, either using render props, higher-order components, or redux.
-
Custom hooks allow you to achieve the same goal without adding components.
-
Hook is a way of reusing state logic without reusing state itself.
-
In fact, each call to a Hook has a completely separate state.
import React, { useLayoutEffect, useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; function useNumber(){ let [number,setNumber] = useState(0); useEffect(()=>{ setInterval(()=>{ setNumber(number=>number+1); }, 1000); } []); return [number,setNumber]; } function Counter1(){let [number,setNumber] = useNumber(); return ( <div><button onClick={()=>{ setNumber(number+1) }}>{number}</button></div> ) } function Counter2(){ let [number,setNumber] = useNumber(); return ( <div><button onClick={()=>{ setNumber(number+1) }}>{number}</button></div> ) } ReactDOM.render(<><Counter1 /><Counter2 /></>, document.getElementById('root'));Copy the code
7. Common Problems
7.1 the use ofeslint-plugin-react-hooksTo check for code errors and give hints.
{ "plugins": ["react-hooks"], // ... "React-hooks ": {"react-hooks/rules-of-hooks": 'error',// Check the rules of this list "react-hooks/ Enumn-deps ": 'WARN' // Check effect dependency}}Copy the code
7.2 Why should Effect be run every time an update is made?
React.docschina.org/docs/hooks-…
7.3 Why does React know which State corresponds to which useState? Why does React know which State corresponds to which useState?
- React relies on the order in which hooks are called. React can maintain the correct Hook state between multiple useState and useEffect calls if you ensure that the hooks are called in the same order every render
function Form() {
// 1. Use the name state variable
const [name, setName] = useState('Mary');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. Use the surname state variable
const [surname, setSurname] = useState('Poppins');
// 4. Use an effect for updating the title
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
Copy the code
/ / -- -- -- -- -- -- -- -- -- -- -- -- first rendering / / / / -- -- -- -- -- -- -- -- -- -- -- -- useState (' Mary ') / / 1. PersistForm state useEffect(persistForm) // 2. Add effect to save form action useState('Poppins') // 3. State useEffect(updateTitle) // 4. Add the effect to update the title / / -- -- -- -- -- -- -- -- -- -- -- -- -- / / second rendering / / -- -- -- -- -- -- -- -- -- -- -- -- -- useState (' Mary ') / / 1. UseEffect (persistForm) // 2. Effect useState('Poppins') // 3. UseEffect (updateTitle) // 4. Replace the update title with effect //...Copy the code
React correctly associates the internal state with the corresponding Hook as long as the order of Hook calls remains consistent across multiple renders. But what happens if we put a Hook call (such as persistForm Effect) into a conditional statement?
// 🔴 using Hook in conditional statements violates the first rule if (name! == '') { useEffect(function persistForm() { localStorage.setItem('formData', name); }); }Copy the code
In the first render name! The conditional value is true, so we will execute the Hook. But the next time we render we may have emptied the form and the expression value will be false. The render will skip the Hook, and the order of Hook calls has changed:
// useEffect(persistForm) // 🔴 This Hook is ignored! UseState ('Poppins') // 🔴 2 (previously 3). UseEffect (updateTitle) // 🔴 3 (previously 4) Failed to read state of surname. Failed to replace effect updating titleCopy the code
React does not know what the second useState Hook should return. React would think that the call to the second Hook in this component would have the same effect on persistForm as the last render, but it does not. From here, subsequent Hook calls are executed ahead of time, resulting in bugs.
If we want to conditionally execute an effect, we can put the judgment inside the Hook:
7.4 User-defined Hook must be useduse
At the beginning?
It has to be. 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.
7.5 Do two components share state using the same Hook?
Don’t. 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.
7.6 Multiple calls in a componentuseState
oruseEffect
Each time a Hook is called, it gets a separate state, completely independent.
7.7 When a component has multiple states, should it merge the states into one state or split the state into multiple state variables?
React.docschina.org/docs/hooks-…
-
Either putting all states in the same useState call or a useState call for each field will work.
-
When you find a balance between these two extremes and combine the related states into several independent state variables, the components become much more readable. If the logic of state starts to get complicated, we recommend using useReducer to manage it, or using custom hooks.
-
If all the states are in the same useState, it is inconvenient to extract that part of the state logic into custom hooks.
7.8 Can I run Effect only on updates?
This is a rare usage scenario. If you need to, you can use a mutable ref to manually store a Boolean value indicating first or later render, and then check for this flag in your effect (if you find yourself doing this a lot, you can create a custom Hook for it).
7.9 When invoking a function in useEffect, the function must be declared in useEffect, not declared externally, and then invoked in useEffect.
function Example({ someProp }) { function doSomething() { console.log(someProp); } useEffect(() => { doSomething(); } []); // 🔴 this is not safe (it calls the 'doSomething' function using 'someProp')}Copy the code
It is difficult to remember which props and states are used by functions outside effect. This is why you usually want to declare the required function inside effect. This makes it easy to see which values in the component scope the effect depends on:
function Example({ someProp }) { useEffect(() => { function doSomething() { console.log(someProp); } doSomething(); }, [someProp]); // ✅ security (our effect only uses' someProp ')}Copy the code
Only if the function (and the function it calls) doesn’t reference props, state, and the values derived from them can you safely omit them from the dependency list. The following example has a Bug:
function ProductPage({ productId }) { const [product, setProduct] = useState(null); async function fetchProduct() { const response = await fetch('http://myapi/product' + productId); // productId prop const json = await response.json(); setProduct(json); } useEffect(() => { fetchProduct(); } []); // 🔴 is invalid because 'fetchProduct' uses' productId '//... }Copy the code
The recommended fix is to move that function inside your effect. This makes it easy to see what props and state your effect is using and make sure they are declared:
function ProductPage({ productId }) { const [product, setProduct] = useState(null); UseEffect (() => {// After moving this function inside effect, we can clearly see the value it uses. async function fetchProduct() { const response = await fetch('http://myapi/product' + productId); const json = await response.json(); setProduct(json); } fetchProduct(); }, [productId]); // ✅ is valid because our effect is only productId //... }Copy the code
7.10 How to gracefully Fetch Data in Hooks
www.robinwieruch.de/react-hooks…
import React, { useState, useEffect } from 'react'; import axios from 'axios'; function App() { const [data, setData] = useState({ hits: [] }); // Every async function returns an implicit promise, but useEffect has a requirement: Or return a function to clear the side effects, Or does not return any content useEffect (async () = > {const result = await axios (' https://hn.algolia.com/api/v1/search?query=redux '); setData(result.data); } []); return ( <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); } export default App;Copy the code
import React, { useState, useEffect } from 'react'; import axios from 'axios'; function App() { const [data, setData] = useState({ hits: [] }); UseEffect () => {const fetchData = async () => {const result = await axios() 'https://hn.algolia.com/api/v1/search?query=redux', ); setData(result.data); }; fetchData(); } []); return ( <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); } export default App;Copy the code
7.11 Do not rely too much on useMemo
-
UseMemo has its own overhead. UseMemo “remembers” some values and, in subsequent render, retrieves the values from the dependent array and compares them to the last recorded value. If they are not equal, the callback is reexecuted, otherwise it returns the “remembered” value. The process itself consumes memory and computing resources. Therefore, excessive use of useMemo may affect the performance of a program.
-
There are three things you should consider before using useMemo:
-
Are the functions passed to useMemo expensive? Some calculations are expensive, so we need to “remember” the return value to avoid recalculating every render. If the operation you are performing is not expensive, there is no need to remember the return value. Otherwise, the cost of using useMemo itself may exceed the cost of recalculating the value. Therefore, for some simple JS operations, we do not need useMemo to “remember” its return value.
-
Is the value returned original? If the computed values are of primitive types (string, Boolean, NULL, undefined, number, symbol), then each comparison is equal and the downstream component is not re-rendered; If the calculated value is a complex type (object, array), even if the value is unchanged, the address will change, causing downstream components to re-render. So we also need to “remember” this value.
-
When writing custom hooks, the return value must be consistent with the reference. Because you can’t be sure how the external is going to use its return value. Bugs can occur if the return value is used as a dependency on other hooks and the references are inconsistent every time re-render (when the value is equal). So if the values exposed in a custom Hook are Object, array, function, etc., useMemo should be used. To ensure that references do not change when the values are the same.
-
7.12 useEffect Cannot accept async as a callback function
UseEffect Receives a function that either returns a function that clears side effects or returns nothing at all. Async returns a promise.
Refer to the article
React-hooks principle learned in react Advance
React Hooks 【 nearly 1W words 】+ project combat
UseEffect and useLayoutEffect