1. React Hooks
- Hooks are new in React 16.8 that allow you to use state and other React features without writing a class
- If you are writing a function component and realize that you need to add some state to it, instead of having to convert it to class, you can now use Hooks in your existing function component
2. Problems solved
- React needs a better native way to share state logic. Hooks allow you to reuse state logic without having to change component code
- Complex components become difficult to understand, Hooks break down the interconnected parts of components into smaller functions (such as setting subscription or requesting data)
- Difficult to understand class, including difficult to understand
this
3. Precautions
- Call Hooks only from the outermost layer of a function, not from loops, conditional judgments, or child functions
- Only use Hooks in React function components, not other JavaScript functions
4. useState
- UseState is an Hooks
- Add some internal state to the component by calling it inside the function component. React retains this state during repeated rendering
- UseState returns a pair of values: the current state and a function that lets you update it, which you can call in an event handler or somewhere else. It is similar to the class component
this.setState
But it won’t put the newstate
And the oldstate
A merger - The only argument to useState is initialization
state
- Returns a
state
, and updatesstate
The function of- During initial rendering, the returned state (
state
) and the first argument passed (initialState
) the same value setState
Function to updatestate
It receives a new state value and enqueues a re-rendering of the component
- During initial rendering, the returned state (
const [state, setState] = useState(initialState);
Copy the code
4.1 the counter
import React from 'react';
class Counter extends React.Component {
state = { number: 0 }
add = () = > {
this.setState({ number: this.state.number + 1 });
}
render() {
return (
<>
<p>{this.state.number}</p>
<button onClick={this.add}>+</button>
</>)}}Copy the code
4.2 Each render is a separate closure
- Each render has its own Props and State
- Each render has its own event handler
- Alert captures the state when the button is clicked
- The component function is called each render, but the number value is constant in each call and is assigned to the current render state value
- Throughout the scope of a single render, the props and state remain the same
import React, { useState } from 'react';
function Counter2() {
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
4.3 Functional update
- If the new
state
Required by using the previousstate
So you can pass the function tosetState
. The function will accept the previousstate
And returns an updated value
import React, { useState } from 'react';
function Counter4() {
let [number, setNumber] = useState(0);
function lazy() {
setTimeout(() = > {
setNumber(number= > number + 1);
}, 3000);
}
return (
<>
<p>{number}</p>
<button onClick={()= > setNumber(number + 1)}>+</button>
<button onClick={lazy}>lazy</button>
</>)}Copy the code
4.4 Inert Initial state
initialState
Parameters are only used in the initial rendering of the component and are ignored in subsequent renderings- If the initial
state
You can pass in a function that evaluates and returns the originalstate
This function is called only during initial rendering - With the class component
setState
The method is different,useState
Update objects are not merged automatically. You can use the functionsetState
Combine extension operators to achieve the effect of merging updated objects
import React, { useState } from 'react';
function Counter5(props) {
console.log('Counter5 render');
function getInitState() {
return { number: props.number };
}
let [counter, setCounter] = useState(getInitState);
// If you change the state and pass the old state directly, do not re-render
return (
<>
<p>{counter.number}</p>
<button onClick={()= > setCounter({ number: counter.number + 1 })}>+</button>
<button onClick={()= > setCounter(counter)}>setCounter</button>
</>)}Copy the code
Why is lazy initialization called: it is initialized only once, and only when used
4.5 Performance Optimization
4.5.1 Object.is
- React skips rendering and Effect execution of child components when the update function of State Hooks is called and the current State is passed in. (use the React
Object.is
Compare algorithms to compare state.)
4.5.2 Reduce rendering times
- Pass in the inline callback function and an array of dependencies as arguments
useCallback
, which returns the Memoized version of the callback function, which is updated only when a dependency changes - Pass in the create function and dependency array as arguments
useMemo
, it recalculates the Memoized value only if a dependency changes. This optimization helps avoid costly calculations on every render.
import React, { useState, memo, useMemo, useCallback } from 'react';
function SubCounter({ onClick, data }) {
console.log('SubCounter render');
return (
<button onClick={onClick}>{data.number}</button>)}// After passing this component to memo, a new component is returned. The new component has a function that does not re-render if the properties remain unchanged
SubCounter = memo(SubCounter);
let oldData, oldAddClick;
function Counter6(props) {
console.log('Counter6 render');
const [name, setName] = useState('counter');
const [number, setNumber] = useState(0);
const data = useMemo(() = > ({ number }), [number]);
console.log('data===oldData ', data === oldData);
oldData = data;
const addClick = useCallback(() = > {
setNumber(number + 1);
}, [number])
console.log('addClick===oldAddClick ', addClick === oldAddClick);
oldAddClick = addClick;
return (
<>
<input type="text" value={name} onChange={(e)= > setName(e.target.value)} />
<SubCounter data={data} onClick={addClick} />
</>)}Copy the code
UseCallback is used for callbacks and useMemo is used for component property data
5. useReducer
useState
Is an alternative to. It receives a similar(state, action)=>newState
thereducer
And returns the currentstate
And what goes with itdispatch
Methods.- In some cases,
useReducer
thanuseState
More applicable, for examplestate
The logic is complex and contains multiple children, or the next onestate
Dependence and beforestate
Etc.
const [state, dispatch] = useReducer(reducer, initialArg, init);
Copy the code
6. useContext
- To receive a
context
Object (React.createContext
) and returns thecontext
The current value - The current
context
The value is the closest to the current grouping value from the upper component<MyContext.Provide>
thevalue
Decision of the - When the component is closest to the upper layer
<MyContext.Provide>
When updated, the Hook triggers re-rendering and uses the latest pass to<MyContext.Provide>
thecontext value
value useContext(MyContext)
The equivalent ofclass
In the componentstatic contextType=MyContext
or<MyContext.Consumer>
useContext(MyContext)
Just so you can read itcontext
And the subscriptioncontext
The change. You still need to use it in the upper component count<MyContext.Provide>
For the underlying componentcontext
import React,{useReducer,createContext,useContext} from 'react';
const initialState = 0;
function reducer(state=initialState,action){
switch(action.type){
case 'ADD':
return {number:state.number+1};
default:
break; }}let CounterContext = createContext();
// The usage of context in function components
function SubCounter_func(){
return (
<CounterContext.Consumer>
{
value=>(
<>
<p>{value.state.number}</p>
<button onClick={()= >value.dispatch({type:'ADD'})}>+</button>
</>
)
}
</CounterContext.Consumer>
)
}
// The usage of context in class components
class SubCounter_class extends React.Component{
static contextTypes = CounterContext
this.context = {state, dispatch}
}
// The usage of useContext
function SubCounter(){
const {state, dispatch} = useContext(CounterContext);
return (
<>
<p>{state.number}</p>
<button onClick={()= >dispatch({type:'ADD'})}>+</button>
</>)}function Counter(){
const [state, dispatch] = useReducer(reducer, initialState, () = >({number:initialState}));
return (
<CounterContext.Provider value={{state, dispatch}} >
<SubCounter/>
</CounterContext.Provider>)}Copy the code
7. effect
- Changing the DOM, adding subscriptions, setting timers, logging, and performing other side effects within the body of a function component (in this case, during the React rendering phase) are not allowed, as this can cause unexplained bugs and break UI consistency
- use
useEffect
Complete the side effects operation. Assigned touseEffect
The function is executed after the component is rendered to the screen. You can think of Effect as an escape route from the purely functional world of React to the imperative world useEffect
Is an Effect Hook, which adds the ability to manipulate side effects to function components. It is the same as the class componentcomponentDidMount
,componentDdUpdate
andcomponentWillUnmount
It serves the same purpose, but has been consolidated into one API- The Hook accepts a function that contains imperative and possibly side effect code
useEffect(didUpdate);
Copy the code
7.1 Modifying the Title using class
import React from 'react';
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've already clickedThe ${this.state.number}Time `;
}
render(){
return (
<>
<p>{this.state.number}</p>
<button onClick={this.add}>+</button>
</>)}}Copy the code
In this class, we need to write duplicate code in two lifecycle functions. This is because, in many cases, we want to do the same thing when a component loads and updates. We want it to be executed after each render, but the React class component doesn’t provide such a method, and even when we extract a method, we still have to call it in two places. UseEffect is executed after the first rendering and after every update
7.2 Effect Implementation
import React,{useState,useEffect} from 'react';
function Counter2(){
let [number,setNumber] = useState(0);
function add(){
setNumber(number+1);
}
useEffect(() = >{
document.title = 'You've already clicked${number}Time `;
});
return (
<>
<p>{number}</p>
<button onClick={add}>+</button>
</>)}Copy the code
Every time we re-render, a new effect is generated to replace the previous one. In a sense, an effect is more like a part of a render result, with each effect belonging to a specific render.
7.3 Eliminating Side effects
- The side effect function can also specify how to eliminate the side effect by returning a function
- To prevent memory leaks, the cleanup function is executed before the component is unloaded. In addition, if the component is rendered multiple times, the previous effect is eliminated before the next effect is executed
import React,{useState,useEffect} from 'react';
function Counter3(){
let [number,setNumber] = useState(0);
let [text,setText] = useState(' ');
useEffect(() = >{
console.log('useEffect');
let $timer = setInterval(() = >{
setNumber(number= >number+1);
},1000);
return () = >{
console.log('destroy effect');
clearInterval($timer); }});return (
<>
<input value={text} onChange={(event)= >setText(event.target.value)}/>
<p>{number}</p>
<button>+</button>
</>)}Copy the code
7.4 Skip Effect for Performance optimization
- If certain values don’t change between rerenders, you can tell React to skip the call to Effect by passing the array as
useEffect
The second optional parameter of - If you want to run 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 does not depend on
props
orstate
Any value in, so it never needs to be repeated
import React,{useState,useEffect} from 'react';
function Counter3(){
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, and the effect function is restarted only if the variable is changed
return (
<>
<input value={text} onChange={(event)= >setText(event.target.value)}/>
<p>{number}</p>
<button>+</button>
</>)}Copy the code
7.5 useRef
useRef
Returns a mutable ref object, where.current
Property is initialized as the passed parameter (initialValue
)- The ref object returned remains constant throughout the life of the component
const refContainer = useRef(initialValue);
Copy the code
7.5.1 useRef
import React,{useRef} from 'react';
function Parent(){
const inputRef = useRef();
function getFocus(){
inputRef.current.focus();
}
return (
<>
<input ref={inputRef}/>
<button onClick={getFocus}>Get focus</button>
</>)}Copy the code
7.5.2 forwardRef
- Route the REF from the parent component to the DOM element in the child component
- The child component accepts props and ref as parameters
import React,{useRef,forwardRef} from 'react';
function Child(props,parentRef){
return <input ref={parentRef}/>
}
let ForwardChild = forwardRef(Child);
function Parent(){
const parentRef = useRef();
function getFocus(){
parentRef.current.focus();
}
return (
<>
<ForwardChild ref={parentRef}/>
<button onClick={getFocus}>Get focus</button>
</>)}Copy the code
7.5.3 useImperativeHandle
useImperativeHandle
Allows you to customize the instance values exposed to the parent component when using ref- In most cases, imperative codes like ref should be avoided,
useImperativeHandle
Should be withforwardRef
Used together
import React,{useRef,forwardRef,useImperativeHandle} from 'react';
function Child(props,parentRef){
let focusRef = useRef();
let inputRef = useRef();
useImperativeHandle(parentRef,() = >{
return {
focusRef,
inputRef,
name:'counter'.focus(){
focusRef.current.focus();
},
changeText(text){ inputRef.current.value = text; }}});return (
<>
<input ref={focusRef}/>
<input ref={inputRef}/>
</>)}let ForwardChild = forwardRef(Child);
function Parent(){
const parentRef = useRef();
function getFocus(){
parentRef.current.focus();
parentRef.current.changeText ('<script>alert(1)</script>');
console.log(parentRef.current.name);
}
return (
<>
<ForwardChild ref={parentRef}/>
<button onClick={getFocus}>Get focus</button>
</>)}Copy the code
8. useLayoutEffect
- Its function signature and
useEffect
Same, but it calls Effect synchronously after all DOM changes - You can use it to read the DOM layout and trigger rerendering synchronously
- Before the browser performs the drawing
useLayoutEffect
Internal update schedules will be refreshed synchronously - Use standard ones whenever possible
useEffect
To avoid blocking view updates
import React,{useLayoutEffect,useEffect,useState} from 'react';
function UseLayoutEffectComponent(){
const [color,setColor] = useState('red');
useLayoutEffect(() = >{
console.log('useLayoutEffect color=',color);
alert('useLayoutEffect color='+color);
});
useEffect(() = >{
console.log('useEffect color=',color);
});
return (
<>
<div id="myDiv" style={{backgroundColor:color}}>color</div>
<button onClick={()= >SetColor () 'red'} > red</button>
<button onClick={()= >SetColor (' yellow ')} > yellow</button>
<button onClick={()= >SetColor (' blue ')} > blue</button>
</>)}Copy the code
9. Customize Hooks
- Sometimes we want to reuse some state logic between components
- Custom hooks allow you to achieve the same goal without adding components
- Hook is a way to reuse state logic, it does not reuse
state
itself - In fact, each call to a Hook has a completely separate
state
- Custom hooks are more of a convention than a feature. If the function name starts with use and calls other hooks, it can be called a custom Hook
9.1 Component lifecycle simulated using Hooks
9.1.1 useIsFirstRender
- Returns whether the component was rendered for the first time
import React from 'react'
export function useIsFirstRender() {
const isFirstRender = React.useRef(true)
if (isFirstRender.current) {
isFirstRender.current = false
return true
}
return isFirstRender.current
}
Copy the code
9.1.2 useMountedStatus
- Returns component mount status
import React from 'react'
export function useMountedStatus() {
const isMount = React.useRef(true)
useWillUnmount(() = > {
isMount.current = false
})
return isMount
}
Copy the code
9.1.3 useDidMount
- Components are mounted successfully
import React from 'react'
export function useDidMount(effect: () => void | Promise<void>) {
React.useEffect(() = > {
effect()
}, [])
}
Copy the code
9.1.4 useWillUnmount
- Components to be unmounted
import React from 'react'
export function useWillUnmount(fn: () => void) {
const fnRef = React.useRef(fn)
fnRef.current = fn
React.useEffect(() = > () = > fnRef.current(), [])
}
Copy the code
9.1.5 useDidUpdate
- Component updates
import React from 'react'
export function useDidUpdate(callback: () => void, deps? : React.DependencyList) {
const isFirstRender = useIsFirstRender()
React.useEffect(() = > {
if(! isFirstRender) { callback() }// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps)
}
Copy the code
9.1.6 useForceUpdate
- Force refresh component
import React from 'react'
export function useForceUpdate() {
const [, setCount] = React.useState(0)
const forceUpdate = React.useCallback(() = > {
setCount(prevCount= > prevCount + 1)}, [])return forceUpdate
}
Copy the code
9.2 Common Hooks tool
9.2.1 useCache
- Caches data and returns a unique reference
- Cached data can be passed into a function that will return the value of the cached function
setCache
Method updates the cache, does not trigger component rerendering, and can be used when the cache is updatedgetCache
Gets the latest cache referenceReact.useRef(getData())
.getData
It is executed every time the component renders, there is no way to pass in a function to calculate the cached valueuseCache(getData)
.getData
Executes when the component is first rendered- When the data consumption of generating the cache is very large, you can use the function to optimize the performance, for example:
const [data] = useCache(() => new Data());
import React from 'react'
import { useIsFirstRender } from '.. /lifecycle'
export function useCache<T> (data: T | (() => T)) {
const cacheRef = React.useRef<T>()
const isFirstRender = useIsFirstRender()
if (isFirstRender) {
cacheRef.current = data instanceof Function ? data() : data
}
const getCache = React.useCallback(() = > cacheRef.current as T, [])
const setCache = React.useCallback((newData: T) = > {
cacheRef.current = newData
}, [])
return [cacheRef.current as T, setCache, getCache] as const
}
Copy the code
9.2.2 useInterval
- The method by which a timer executes repeatedly
import React from 'react'
export function useInterval(handler: () => void, timeout? : number) {
const timer = React.useRef<number | undefined> ()const callback = React.useRef(handler)
const stop = React.useCallback(() = > {
window.clearInterval(timer.current)
}, [])
const start = React.useCallback(() = > {
stop()
timer.current = window.setInterval(callback.current, timeout)
}, [stop, timeout])
return { start, stop }
}
Copy the code
9.2.3 usePersistFn
- In some scenarios, you might need to remember a callback using useCallback, but since the internal function must be recreated frequently, the memory is not very good, causing the child component to repeat render.
- For super complex subcomponents, rerendering can have an impact on performance. With usePersistFn, the function address is guaranteed to never change.
import { useCallback, useRef } from 'react'
export type noop = (. args: any[]) = > any
export function usePersistFn<T extends noop> (fn: T) {
const ref = useRef<any>(() = > {
throw new Error('Cannot call function while rendering.')
})
ref.current = fn
const persistFn = useCallback(((. args) = >ref.current(... args))as T, [ref])
return persistFn
}
Copy the code
9.2.4 useSetState
- extension
React.useState
, making the support similarthis.setState
The merger of the originalstate
The function of the setState
Refer to the samesetState
Is a functionnull
The component is not updated
import React from 'react'
export function useSetState<S extends AnyObject> (initialState: S | (() => S)) {
const [state, updateState] = React.useState(initialState)
const setState = React.useCallback<SetStateLikeMethod<S>>(inputState= > {
updateState(s= > {
const newState = inputState instanceof Function ? inputState(s) : inputState
returnnewState ? {... s, ... newState } : s }) }, [])return [state, setState] as const
}
Copy the code