React hooks:
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. Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class.
Hooks related to hooks:
The hook, | role |
---|---|
useState | Initialize and set state |
useEffect | ComponentDidMount, componentDidUpdate and componentWillUnmount, so you can listen for useState definition changes |
useContext | Define a global object, like context |
useReducer | Functions can be enhanced to provide redux-like functionality |
useCallback | The first parameter is an anonymous function, which is the body of the function we want to create. The second argument is an array of variables that determine whether the function body needs to be recreated. If the value of the variable passed in remains unchanged, the result of the memory is returned. If any of these items change, a new result is returned |
useMemo | UseCallback returns a function, and useDemo returns a value |
useRef | Gets the DOM corresponding to the ref attribute |
useLayoutEffect | The useEffect is the same, but fires synchronously after all DOM changes |
useState
- React assumes that when you call useState multiple times, you can guarantee their * each time you render** Call order *** is constant.
- Add some internal state to the component by calling it inside the function component. React retains this state during repeated rendering
- The only argument to useState is the initial state
- UseState returns an array (deconstructed from the array), a state, and a function that updates 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 in 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
// We can call it whatever we want, because we're returning an array
const [state, setState] = useState(initialState);
Copy the code
import React, { useState } from 'react'; Const [count, setCount] = useState(0); function Example() {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
1.1 Each render is a separate closure
- Each render 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, so that each rendering is independent and the fetched value is not affected by subsequent operations
import { useState } from 'react' import { PageContainer } from '@ant-design/pro-layout'; import { Card, Button, message } from 'antd'; const Ceshi1 = () => { let [number, setNumber] = useState(0); Const alertNumber = () => {setTimeout(() => {// alert only gets the status when the button is clicked message.success(number); }, 2000); } return ( <PageContainer> <Card> <p>{number}</p> <Button onClick={() => setNumber(number + 1)}>+</Button> <Button onClick={alertNumber}>alertNumber</Button> </Card> </PageContainer> ) } export default Ceshi1Copy the code
1.2 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.
import { useState } from 'react' import { PageContainer } from '@ant-design/pro-layout'; import { Card, Button, message } from 'antd'; const Ceshi1 = () => { let [number, setNumber] = useState(0); Const lazy = () => {setTimeout(() => {const lazy = () => {setTimeout(() => {const lazy = () => {setTimeout(() => {const lazy = () => {setTimeout(() => {const lazy = () => { setNumber(number => number + 1); }, 3000); } return ( <PageContainer> <Card> <p>{number}</p> <Button onClick={() => setNumber(number + 1)}>+</Button> <Button onClick={lazy}>lazy</Button> </Card> </PageContainer> ) } export default Ceshi1Copy the code
1.3 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
import { useState } from 'react'
import { PageContainer } from '@ant-design/pro-layout';
import { Card, Button, message } from 'antd';
const Ceshi1 = (props: any) = > {
let [counter, setCounter] = useState(getInitState);
// This function is executed only once during the initial render, and will not be called again when the component is re-rendered by updating the state
function getInitState() {
const initNum = (4+8) /3
return { number: initNum};
}
return (
<PageContainer>
<Card>
<p>{counter.number}</p>
<Button onClick={()= > setCounter({ number: counter.number + 2})}>+</Button>
<Button onClick={()= > setCounter(counter)}>setCounter</Button>
</Card>
</PageContainer>)}export default Ceshi1
Copy the code
1.4 useState replaces state with object. is
- The Hook uses object. is internally to compare whether the new/old state is equal
- 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 If other states can be calculated, there is no need to declare states separately
A state must not be computed directly from other states /props, otherwise state is not defined.
const SomeComponent = (props) = > {
const [source, setSource] = useState([
{type: 'done'.value: 1},
{type: 'doing'.value: 2},])const [doneSource, setDoneSource] = useState([])
const [doingSource, setDoingSource] = useState([])
useEffect(() = > {
setDoingSource(source.filter(item= > item.type === 'doing'))
setDoneSource(source.filter(item= > item.type === 'done'))
}, [source])
return (
<div>.</div>)}Copy the code
In the example above, the variables doneSource and doingSource can be calculated using the source variable, so don’t define doneSource and doingSource!
const SomeComponent = (props) = > {
const [source, setSource] = useState([
{type: 'done'.value: 1},
{type: 'doing'.value: 2},])const doneSource = useMemo(() = > source.filter(item= > item.type === 'done'), [source]);
const doingSource = useMemo(() = > source.filter(item= > item.type === 'doing'), [source]);
return (
<div>.</div>)}Copy the code
In general, such problems are obscure in projects and pass through layers, which is hard to see at a glance in Code Review. If the variables can be clearly defined, that’s half the battle.
1.6 Ensure unique data sources
Ensure that the same data is stored in only one place within a project.
Do not exist in redux and define a state store in the component.
Do not have a state store defined in the current component that exists in the parent component.
Do not have a URL Query and define a state store in the component.
function SearchBox({ data }) {
const [searchKey, setSearchKey] = useState(getQuery('key'));
const handleSearchChange = e= > {
const key = e.target.value;
setSearchKey(key);
history.push(`/movie-list? key=${key}`);
}
return (
<input
value={searchKey}
placeholder="Search..."
onChange={handleSearchChange}
/>
);
}
Copy the code
In the above example, the searchKey is stored in two places, both on the URL Query and with a state defined. It can be optimized as follows:
function SearchBox({ data }) {
constsearchKey = parse(localtion.search)? .key;const handleSearchChange = e= > {
const key = e.target.value;
history.push(`/movie-list? key=${key}`);
}
return (
<input
value={searchKey}
placeholder="Search..."
onChange={handleSearchChange}
/>
);
}
Copy the code
In actual project development, such problems are also relatively obscure, and should be paid attention to when coding.
1.7 useState is properly merged
Have you ever written code in a project like this:
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
const [school, setSchool] = useState();
const [age, setAge] = useState();
const [address, setAddress] = useState();
const [weather, setWeather] = useState();
const [room, setRoom] = useState();
Copy the code
Anyway, I originally wrote that the useState split was too thin, resulting in a large useState in the code.
I suggest that variables with the same meaning can be combined into a single state to make the code much more readable:
const [userInfo, setUserInfo] = useState({
firstName,
lastName,
school,
age,
address
});
const [weather, setWeather] = useState();
const [room, setRoom] = useState();
Copy the code
When changing a variable, make sure you include the old field, for example, if you just want to change firstName:
setUserInfo(s= > ({
...s,
fristName,
}))
Copy the code
React Class state merges automatically:
this.setState({
firstName
})
Copy the code
In Hooks, can this be used? This is possible because we can package Hooks. For example, useSetState of ahooks encapsulates similar logic:
const [userInfo, setUserInfo] = useSetState({
firstName,
lastName,
school,
age,
address
});
// Automatic merge
setUserInfo({
firstName
})
Copy the code
useEffect
- Effect: Refers to logic that does not occur during the conversion of data to view, such as
ajax
Request, access nativedom
Element, local persistent cache, bind/unbind events, add subscriptions, set timers, log, and more. - UseEffect is an Effect Hook that gives function components the ability to manipulate side effects. It is in the class component
componentDidMount
,componentDidUpdate
和componentWillUnmount
A combination of three apis - 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
- with
componentDidMount
或componentDidUpdate
In contrast, Effect scheduled with useEffect does not 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), a separate useLayoutEffect Hook with the same API as useEffect can be used.
useEffect(effect, array)
Copy the code
Effect is triggered each time a render is completed, in conjunction with Array to simulate the life cycle of the class
- If not, every time componentDidUpdate returnFunction (if present) is raised, then Effect is raised
- Simulation componentDidMount []
- [id] Fires only after the value of id changes
- Clear effect: The component handles memory problems when uninstalling, such as clearing timers and clearing event listeners
useEffect(() = > {
ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);
return () = > {
ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);
};
},[id]);
Copy the code
useEffect
1. First time only componentDidMount can be used to request asynchronous data… ,
UseEffect Finally, the addition of [] indicates only the first execution
UseEffect (()=>{const users = get user info ()},[])Copy the code
2. Instead of life functions such as willUpdate that are executed every render
UseEffect Finally, without [], every render is executed
UseEffect (()=>{const users = get user info every time ()})Copy the code
3. It takes a bit of money to execute every render, so:
UseEffect finally, add [], and the field in [] indicates that the field has changed before this effect is executed
UseEffect (() => {const users = (name)},[name])Copy the code
4. What if I wanted to separate name and age:
Multiple useeffects can be written
},[name]) useEffect(() => {const users = (const users =)},[name]) useEffect(() => {const users = },[age]Copy the code
5. If we subscribe to something and then unsubscribe during the willUnMount life cycle, how can we do this with useEffect:
You can unsubscribe from an effect return
UseEffect (() => {const subscription = Return () => {unsubscribe from the national people eat intelligence! }}, [])Copy the code
Why unsubscribe?
UseEffect is used after render. If useEffect has a setInterval… Every time render is rendered, useEffect creates another setInterval, and chaos ensued… You can delete the following example return and feel it
useEffect(() => { console.log('use effect... ',count) const timer = setInterval(() => setCount(count +1), 1000) return ()=> clearInterval(timer) })Copy the code
6. Some other rules for useEffect:
The value of state used in useEffect is fixed in useEffect and will not be changed unless useEffect refreshes and resets the value of state
const [count, setCount] = useState(0) useEffect(() => { console.log('use effect... ',count) const timer = setInterval(() => { console.log('timer... Count :', count) setCount(count + 1)}, 1000) return ()=> clearInterval(timer)},[]Copy the code
useLayoutEffect
-
Similar to useEffect, page flickering in some feature scenarios can be resolved by synchronizing status updates
-
This is mainly used when dealing with the DOM. You need to use this when your useEffect operation needs to process the DOM and change the style of the page, otherwise it may cause flash screen problems. The callback function in useLayoutEffect executes immediately after the DOM update is complete, but runs before the browser can do any drawing, blocking the browser’s drawing.
-
A quick word about the differences:
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
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). It is not only used to manage DOM refs, it is equivalent to this, and can store any variable, which is a good way to avoid the convenience of closures.
Why useRef?
It’s not just for managing DOM refs, it’s the equivalent of this, and can hold any variable, which is a great way to avoid the inconvenience of closures.
How to use useRef?
const [count, setCount] = useState<number>(0) const countRef = useRef<number>(count) Copy the code
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
Fix references –useRef returns the same ref object every time it renders (using react. createRef, refs are recreated every time the component is re-rendered)
Compare createRef – there is no difference between the two during the initialization phase, but there is a difference during the update phase.
We know that in a local function, the function’s variables are regenerated each time the function is updated. So every time we updated the component, we re-created the ref, and it was obviously not appropriate to continue using createRef, so useRef was introduced. The ref created by useRef acts as if it were a global variable defined outside the function and is not recreated as the component is updated. But when the component is destroyed, it also disappears without manual destruction
4.1 Solve the closure problem
It’s not just for managing DOM refs, it’s the equivalent of this, and can hold any variable, which is a great way to avoid the inconvenience of closures.
As mentioned earlier:
The value of state used in useEffect is fixed in useEffect and will not be changed unless useEffect refreshes and resets the value of state
const [count, setCount] = useState(0) useEffect(() => { console.log('use effect... ',count) const timer = setInterval(() => { console.log('timer... count:', count) setCount(count + 1) }, 1000) return ()=> clearInterval(timer) },[])Copy the code
The useEffect state value is fixed, and the solution is to use the useRef function:
The global scope is modified in one place and updated everywhere else.
4.2 Due to change.current
Property does not cause the component to rerender. This property allows us to get the previous value of the state:
const Counter = () = > {
const [count, setCount] = useState<number>(0)
const preCountRef = useRef<number>(count)
useEffect(() = > {
preCountRef.current = count
})
return (
<div>
<p>pre count: { preCountRef.current }</p>
<p>current count: { count }</p>
<button onClick={()= >> add setCount (count + 1)}</button>
</div>)}Copy the code
As you can see from the result, the previous value of the state is always displayed:
pre count: 4
current count: 5
Copy the code
useMemo
5.1 Why Use useMemo?
You know from useEffect that you can influence the execution of certain functions by passing parameters to it. React checks to see if these parameters have changed, and does so only if there is a difference.
UseMemo does something similar, assuming there are a large number of methods and you only want to run them when their parameters change, not every time a component is updated, you can use useMemo for performance optimization.
Remember that functions passed into useMemo are executed during rendering. Please do not perform non-rendering operations inside this function. Operations such as side effects are used by useEffect, not useMemo.
5.2 How do I Use useMemo?
function changeName(name) {
return name + 'Do something to name and return a new name'
}
const newName = useMemo(() = > {
return changeName(name)
}, [name])
Copy the code
5.3 Scenario Examples
5.3.1. Routine use, avoiding repeated execution of unnecessary methods:
Let’s start with a very simple example. Here’s the code that doesn’t use useMemo yet:
import React, { useState, useMemo } from 'react'
/ / the parent component
const Example = () = > {
const [time, setTime] = useState<number>(0)
const [random, setRandom] = useState<number>(0)
return (
<div>
<button onClick={()= >SetTime (new Date().getTime())}> Get the current time</button>
<button onClick={()= >SetRandom (math.random ())}> Gets the current random number</button>
<Show time={time}>{random}</Show>
</div>
)
}
type Data = {
time: number
}
/ / child component
const Show:React.FC<Data> = ({ time, children }) = > {
function changeTime(time: number) :string {
console.log('changeTime excuted... ')
return new Date(time).toISOString()
}
return (
<div>
<p>Time is: { changeTime(time) }</p>
<p>Random is: { children }</p>
</div>)}export default Example
Copy the code
In this example, the changeTime method in the
In fact, clicking on the get current random number button will only change the children parameter, but our changeTime will also be re-executed due to the re-rendering of the child component, which is unnecessary and consumes irrelevant performance.
Modify our
const Show:React.FC<Data> = ({ time, children }) = > {
function changeTime(time: number) :string {
console.log('changeTime excuted... ')
return new Date(time).toISOString()
}
const newTime: string = useMemo(() = > {
return changeTime(time)
}, [time])
return (
<div>
<p>Time is: { newTime }</p>
<p>Random is: { children }</p>
</div>)}Copy the code
The changeTime function will be executed only if you click on the current time, and clicking on the current random number will no longer trigger the function.
5.3.2. UseEffect is different from useMemo
- UseEffect is in
Triggered when the DOM changes
UseMemo inTriggered before DOM renders
- UseMemo is in
Triggered before DOM update
As it is officially said, the analogy lifecycle is shouldComponentUpdate - UseEffect can help us in the
After the DOM update is complete, some side effects are performed
Data fetching, setting up subscriptions, and manually changing the DOM in the React component - Not inside the useMemo function
Perform operations unrelated to rendering
, such asOperations such as side effects fall under the category of useEffect
Instead of useMemo - Used in useMemo
SetState and you'll see that it creates an endless loop
And there are warnings because useMemo isIn the render
You operate in itDOM
After that, it will trigger againmemo
You may be wondering, can’t useMemo do what it does with useEffect? The answer is no! If you add the following code to a child component:
const Show:React.FC<Data> = ({ time, children }) = > {
/ /...
useEffect(() = > {
console.log('effect function here... ')
}, [time])
const newTime: string = useMemo(() = > {
return changeTime(time)
}, [time])
/ /...
}
Copy the code
You’ll notice that the console will print the following:
> changeTime excuted...
> effect function here.Copy the code
As we said at the beginning: functions passed into useMemo are executed during rendering. Here we have to mention react. memo, which implements the Pure function of the entire component:
const Show:React.FC<Data> = React.memo(({ time, children }) => {... }Copy the code
The difference between useMemo and React. Memo is that useMemo in some cases does not want the component to perform shallow comparisons to all props, but only to implement local Pure functions, that is, only to compare specific props and decide whether to update locally or not.
5.3.3. UseMemo and React.memo
React.memo
- The parcel
The react components
If the props from the parent component are not changed, the child component will not be rendered - The second argument can be passed as a judge
isEqual
, availablepreProps
andprops
Returns a Boolean value to determine whether to update the render component
useMemo
useMemo
Can be used for processingFiner granularityIf a part of the component is cached, the callback will only be executed if the second parameter is updated.Get the latest variable/component, otherwise unchanged.useCallback
The principle is the same, the difference is, it’s to avoid double definition of the function, one kindCaching of functions
5.3.4. Use useMemo appropriately (not cost-effective for simple type calculations)
UseMemo may not be cost-effective when we are dealing with very simple base-type calculations.
const a = 1;
const b = 2;
const c = useMemo(() = > a + b, [a, b]);
Copy the code
In the example above, what is the cost of calculating a+b? Or record a/ B and compare whether a/ B changes are costly?
Obviously a plus B consumes less.
const a = 1;
const b = 2;
const c = a + b;
Copy the code
You can do this on your own, so if it’s a simple, basic type of calculation, don’t use useMemo
useCallback
6.1 useCallback Does not improve performance in most scenarios
UseCallback can remember functions and avoid repeated generation of functions, so that functions can be passed to child components without repeated rendering, improving performance.
const someFunc = useCallback(() = >{ doSomething(); } []);return <ExpensiveComponent func={someFunc} />
Copy the code
So with that in mind, when I started, whenever it was a function, I added a useCallback.
However, we should note that there is another requirement to improve performance. The child component must use shouldComponentUpdate or React. Memo to repeat rendering ignoring the same parameters.
If the ExpensiveComponent component is just a generic component, it does not have any use. Like this:
const ExpensiveComponent = ({ func }) = > {
return (
<div onClick={func}>
hello
</div>)}Copy the code
ExpensiveComponent must be covered with react. Memo to avoid repeated rendering with unchanged parameters and improve performance.
const ExpensiveComponent = React.memo(({ func }) = > {
return (
<div onClick={func}>
hello
</div>)})Copy the code
So, useCallback is going toshouldComponentUpdate/React.memo
For supporting use. Of course, I recommend that you leave performance tuning out of general projects, that is, don’t use useCallback, unless you have a very complex component that can be used alone.
6.2 useCallback makes code less readable
I’ve seen some code that looks something like this with useCallback:
const someFuncA = useCallback((d, g, x, y) = > {
doSomething(a, b, c, d, g, x, y);
}, [a, b, c]);
const someFuncB = useCallback(() = > {
someFuncA(d, g, x, y);
}, [someFuncA, d, g, x, y]);
useEffect(() = >{
someFuncB();
}, [someFuncB]);
Copy the code
In the above code, variables are passed layer by layer, and it can be a headache to determine which variable changes trigger useEffect execution.
I wish I didn’t use useCallback and just wrote the function naked:
const someFuncA = (d, g, x, y) = > {
doSomething(a, b, c, d, g, x, y);
};
const someFuncB = () = > {
someFuncA(d, g, x, y);
};
useEffect(() = >{ someFuncB(); }, [...]. );Copy the code
6.3 Differences between useCallback and useMemo
useMemo
和useCallback
The parameters received are the same, they are executed after their dependencies have changed, and they return cached values, with the differenceuseMemo
Returns the result of the function’s execution,useCallback
The function is returned.
const renderButton = useCallback(
() = > (
<Button type="link">
{buttonText}
</Button>
),
[buttonText] // renderButton is rerendered when buttonText changes
);
Copy the code
UseMemo returns a value
To avoid costly calculations on every render
const result = useMemo(() = > {
for (let i = 0; i < 100000; i++) {
(num * Math.pow(2.15)) / 9;
}
}, [num]);
Copy the code
Personal advice: Don’t use useCallback if you have nothing to do with it. Note that useCallback can be optimized using useMemo for some computation operations.
6.4 Using useMemo, React. Memo and useCallback, let’s look at a specific example:
- The parent component will have one
Value passed to child components
If other values of the parent component change, the child component will also be rendered several times, resulting in performance waste; UseMemo passes the parent component toThe values of the child components are cached
The child component is rerendered only when the state of the second parameter in the useMemo changes - UseMemo is used for this
Caches the result of the execution of this function
Is recalculated only if the dependency changes
Note that useMemo caches the result of the function's execution and only retracks it if [count, price] changesconst Parent = () = > {
const [count, setCount] = useState(0);
const [color,setColor] = useState("");
const [price,setPrice] = useState(10);
const handleClick = () = > {
setCount(count + 1);
}
const getTotal = useMemo(() = >{
console.log("getTotal exec ...")
return count * price
},[count, price])
return (<div>
<div> <input onChange={(e)= > setColor(e.target.value)}/></div>
<div> <input value={price} onChange={(e)= > setPrice(Number(e.target.value))}/></div>
<div> {count} <button onClick={handleClick}>+ 1</button></div>
<div> {getTotal}</div>
</div>)}Copy the code
memo
- A change in the Parent component’s name attribute or text attribute causes the Parent function to be reexecuted, so even if the child component is passed without any changes or even if the child component doesn’t depend on any props properties,
Causes child components to be re-rendered
- When using memo to wrap child components,
Subcomponents are rerendered only if props changes
To improve performance
const Child = memo((props: any) = > {
console.log("Sub-component update..."); // Subcomponents are rerendered only when the props property changes and the name property changes
return (
<div>
<h3>Child components</h3>
<div>text:{props.name}</div>
<div>{new Date().getTime()}</div>
</div>)})const Parent = () = > {
const [text, setText] = useState("")... ... <Child name ={text}/> }Copy the code
- Use the memoAPI to cache components
import React, { memo } from "react"
const CacheComponent = memo(() = > {
return <div>^ ^</div>
})
Copy the code
useCallback
- The parent component will have one
【 Method 】 Pass to child components
If other states of the parent component change, the child component will followRender times
, will cause performance waste; Usecallback is passed to the parent componentSubcomponent methods are cached
, only if the state of the second parameter in the USecallback changesTo render
- But if passed the props
Contains the function
, the parent component is re-rendered each timeIt's all about creating new functions
, so the transfer function subcomponent will stillTo render
Even though the content of the function is still the same, we want to putFunctions are also cached
, thus introducing the useCallback
const Child = memo((props: any) = > {
console.log("Sub-component update..."); // The Parent function has something changed, the Child is re-executed, handleInputChange already points to the new function instance, so the Child component is still refreshed
return (
<div>
<div>text:{props.name}</div>
<div> <input onChange={props.handleInputChange} /></div>
</div>)})const Parent = () = > {
const [text, setText] = useState("")
const [count, setCount] = useState(0)
const handleInputChange =useCallback((e) = > {
setText(e.target.value )
},[])
return (<div>... ...<Child name={text} handleInputChange={handleInputChange}/>
</div>)}Copy the code
- UseCallback is used to cache functions. The function is re-executed only when the dependency changes. The function in the parent component is passed as props to the child component.
Functions as props also generate new instances
, resulting in the refresh of the child component using useCallback can cache the function.It should be used with the memo
1- handleInputChange is wrapped with useCallback in the Parent component2- Currently handleInputChange does not depend on any items, so handleInputChange is only called once during initialization and is cachedconst handleInputChange =useCallback((e) = > {
setText(e.target.value )
},[])
3- Add count to the dependency. If count changes, a new function is generated, changing the value of count inside the functionconst handleInputChange =useCallback((e) = > {
setText(e.target.value )
},[count])
Copy the code
useContext
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
When the most recent <MyContext.Provider> update is made to the component’s upper layer, the Hook triggers a rerender and uses the latest context value passed to MyContext Provider
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
const context = useContext(Context)
Copy the code
UseContext, as its name suggests, uses React Context in a Hook manner. In short, it is a producer-consumer mode
import React, { useContext, useState, useEffect } from "react";
const ThemeContext = React.createContext(null);
const Button = () => {
const { color, setColor } = useContext(ThemeContext);
useEffect(() => {
console.info("Context changed:", color);
}, [color]);
const handleClick = () => {
console.info("handleClick");
setColor(color === "blue" ? "red" : "blue");
};
return (
<button
type="button"
onClick={handleClick}
style={{ backgroundColor: color, color: "white" }}
>
toggle color in Child
</button>
);
};
// app.js
const Ceshi2 = () => {
const [color, setColor] = useState("blue");
return (
<ThemeContext.Provider value={{ color, setColor }}>
<h3>
Color in Parent: <span style={{ color: color }}>{color}</span>
</h3>
<Button />
</ThemeContext.Provider>
);
};
export default Ceshi2
Copy the code
useReducer
- UseReducer is like Reducer in Redux, which is a syntactic sugar for hooks
- 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
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
Why use useReducer? For example, have you ever wondered what it would feel like to write a lot of useState in a component, like this code:
const [name, setName] = useState(''); // const [PWD, setPwd] = useState(" "); // password const [isLoading, setIsLoading] = useState(false); Loading const [error, setError] = useState(" "); Const [isLoggedIn, setIsLoggedIn] = useState(false); // Whether to log inCopy the code
UseState version login
Let’s take a look at the general implementation of the login page using useState:
function LoginPage() { const [name, setName] = useState(''); // const [PWD, setPwd] = useState(" "); // password const [isLoading, setIsLoading] = useState(false); // Whether to display loading, sending request const [error, setError] = useState("); Const [isLoggedIn, setIsLoggedIn] = useState(false); // whether to login const login = (event) => {event.preventdefault (); setError(''); setIsLoading(true); login({ name, pwd }) .then(() => { setIsLoggedIn(true); setIsLoading(false); }). Catch ((error) => {// Login failure: display error information, clear the input box user name, password, clear the loading flag setError(error. Message); setName(''); setPwd(''); setIsLoading(false); }); } return (// return page JSX Element)}Copy the code
In the previous Demo, we defined five states to describe the state of the page. In the login function, a series of complex state Settings are carried out when the login succeeds or fails. You can imagine that as the requirements become more complex and more states are added to the page, more setStates are scattered all over the place, and it is easy to set up errors or omissions. Maintaining such old code is a nightmare.
UseReducer version login
Let’s see how to use useReducer to modify this code. First, a brief introduction to useReducer.
const [state, dispatch] = useReducer(reducer, initState);
Copy the code
The useReducer accepts two arguments:
The first parameter, the Reducer function, is exactly what we introduced in our last article. Second argument: initialization state. The return values are the latest state and dispatch functions (used to trigger the Reducer function to calculate the corresponding state). For complex state operation logic, nested state objects are recommended to use useReducer.
As abstract as it sounds, let’s start with a simple example:
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 (<> // useReducer will rerender Count according to the dispatch action, rerender Count: <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }Copy the code
Use useReducer to modify the login demo.
const initState = { name: '', pwd: '', isLoading: false, error: '', isLoggedIn: false, } function loginReducer(state, action) { switch(action.type) { case 'login': return { ... state, isLoading: true, error: '', } case 'success': return { ... state, isLoggedIn: true, isLoading: false, } case 'error': return { ... state, error: action.payload.error, name: '', pwd: '', isLoading: false, } default: return state; } } function LoginPage() { const [state, dispatch] = useReducer(loginReducer, initState); const { name, pwd, isLoading, error, isLoggedIn } = state; const login = (event) => { event.preventDefault(); dispatch({ type: 'login' }); login({ name, pwd }) .then(() => { dispatch({ type: 'success' }); }) .catch((error) => { dispatch({ type: 'error' payload: { error: error.message } }); }); } return (// return page JSX Element)}Copy the code
At first glance, useReducer’s code is longer, but it is clear that the second version has better readability and we can understand the logic of state changes more clearly.
As you can see, the login function now more clearly expresses the user’s intent, starting with login, login SUCCESS, and login error. The LoginPage does not need to care about how to deal with these behaviors. That is what the loginReducer needs to care about, performance and business separation.
Another benefit is that all state processing is centralized, giving us more control over state changes and making it easier to reuse state logic change code, such as dispatch({type: ‘error’}) in other functions that also need to trigger the login error state.
UseReducer allows us to separate what from how. Dispatch ({type: ‘login’}); dispatch({type: ‘login’}); ‘logout’}), all how-related codes are maintained in reducer, and the component only needs to think about What, so that our code can be more clear like the user’s behavior.
Customize the Hook
- Custom hooks are more of a convention than a feature. If the function name begins with use and it calls another Hook, it is a custom Hook. Generally I classify hooks into these categories
util
As the name implies, utility classes such as useDebounce, useInterval, useWindowSize, and so on. For example, useWindowSize
import React, { useState, useCallback, useEffect } from "react";
export const useWinSize = () = > {
// 1. Use useState to initialize window size state
const [size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
});
const changeSize = useCallback(() = > {
// useCallback caches functions and throttles them
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight }); } []);// 2. Use useEffect to listen for resize events at component creation and reset state at resize (use useCallback throttling)
useEffect(() = > {
// Bind once the page listener event component is unbound when it is destroyed
window.addEventListener("resize", changeSize);
return () = > {
window.removeEventListener("resize", changeSize); }; } []);return size;
};
Copy the code
Using this custom hook within a component:
import React from "react";
import { useWinSize } from ".. /hooks";
export default() = > {const size = useWinSize();
return (
<div>Page size: '{size.width}*{size.height}</div>
);
};
Copy the code
API
For example, if we have a public city list interface, we can put it in global public when we use Redux, otherwise we may need to copy and paste it. With hooks we just need to use them and then reuse them elsewhere
import { useState, useEffect } from 'react';
import { getCityList } from '@/services/static';
const useCityList = (params) = > {
const [cityList, setList] = useState([]);
const [loading, setLoading] = useState(true)
const getList = async() = > {const { success, data } = await getCityList(params);
if (success) setList(data);
setLoading(false)}; useEffect(() = >{getList(); }, []);return {
cityList,
loading
};
};
export default useCityList;
// bjs
function App() {
// ...
const { cityList, loading } = useCityList()
// ...
}
Copy the code
logic
Logic class, for example, we have a click user profile picture to follow the user or unfollow the logic, may be used in the comment list, user list, we can do this
import { useState, useEffect } from 'react';
import { followUser } from '@/services/user';
const useFollow = ({ accountId, isFollowing }) = > {
const [isFollow, setFollow] = useState(false);
const [operationLoading, setLoading] = useState(false)
const toggleSection = async () => {
setLoading(true)
const { success } = await followUser({ accountId });
if(success) { setFollow(! isFollow); } setLoading(false)}; useEffect(() = > {
setFollow(isFollowing);
},
[isFollowing],
);
return {
isFollow,
toggleSection,
operationLoading
};
};
export default useFollow;
Copy the code
conclusion
First of all, as a user who fully embraces React Hooks, I fully appreciate the optimizations that React Hooks bring. But it has to be said that Hooks are a double-edged sword in many cases and have a number of caveats when they are used.
Note: The above is my opinion on React Hooks, please point out if anything is wrong.