Basic use of Hooks
A problem fixed by Hooks
- A functional component cannot have its own state. Function components before hooks are stateless and get the state of the parent component through props, but hooks provide useState to maintain state inside function components.
- Function components cannot listen to the lifecycle of a component. UseEffect aggregates multiple lifecycle functions.
- The class component life cycle is complex (it changed from version 15 to version 16)
- Class component logic is hard to reuse (HOC Render props)
Hooks compare the benefits of class
- It’s a little bit more concise
The class components
class ExampleOfClass extends Component {
constructor(props) {
super(props)
this.state = {
count: 1
}
}
handleClick = () = > {
let { count } = this.state
this.setState({
count: count+1})}render() {
const { count } = this.state
return (
<div>
<p>you click { count }</p>
<button onClick={this.handleClick}>Click on the</button>
</div>)}}Copy the code
hooks
function ExampleOfHooks() {
const [count, setCount] = useState(0)
const handleClick = () = > {
setCount(count + 1)}return (
<div>
<p>you click { count }</p>
<button onClick={handleClick}>Click on the</button>
</div>)}Copy the code
- Business code is more aggregated
When using class components, it is common for a function to appear in two lifecycle functions, which can sometimes be forgotten if written separately. Such as:
let timer = null
componentDidMount() {
timer = setInterval(() = > {
// ...
}, 1000)}// ...
componentWillUnmount() {
if (timer) clearInterval(timer)
}
Copy the code
Since adding the timer and scavenger is in two different lifecycle functions, there may be a lot of other business code in between, so it is possible to clear the timer, if not adding a scavenger function when the component is unloaded, it may cause a memory leak, and the network is always requesting it.
But using hooks makes code easier to manage, easier to forget.
useEffect(() = > {
let timer = setInterval(() = > {
// ...
}, 1000)
return () = > {
if (timer) clearInterval(timer)
}
}, [/ /... ] )
Copy the code
- Logic reuse convenience
React hooks provide custom multiplexing logic for class components using both render props and HOC.
Take the logical reuse of the location of the mouse on the page as an example.
Class component render props
import React, { Component } from 'react'
class MousePosition extends Component {
constructor(props) {
super(props)
this.state = {
x: 0.y: 0
}
}
handleMouseMove = (e) = > {
const { clientX, clientY } = e
this.setState({
x: clientX,
y: clientY
})
}
componentDidMount() {
document.addEventListener('mousemove'.this.handleMouseMove)
}
componentWillUnmount() {
document.removeEventListener('mousemove'.this.handleMouseMove)
}
render() {
const { children } = this.props
const { x, y } = this.state
return(
<div>
{
children({x, y})
}
</div>)}}/ / use
class Index extends Component {
constructor(props) {
super(props)
}
render() {
return (
<MousePosition>
{
({x, y}) => {
return (
<div>
<p>x:{x}, y: {y}</p>
</div>)}}</MousePosition>)}}export default Index
Copy the code
Custom hooks reuse
import React, { useEffect, useState } from 'react'
function usePosition() {
const [x, setX] = useState(0)
const [y, setY] = useState(0)
const handleMouseMove = (e) = > {
const { clientX, clientY } = e
setX(clientX)
setY(clientY)
}
useEffect(() = > {
document.addEventListener('mousemove', handleMouseMove)
return () = > {
document.removeEventListener('mousemove', handleMouseMove)
}
})
return [
{x, y}
]
}
/ / use
function Index() {
const [position] = usePosition()
return(
<div>
<p>x:{position.x},y:{position.y}</p>
</div>)}export default Index
Copy the code
It is clear that using hooks is much easier to reuse logic and much clearer when used.
Hooks common API use
- useState
grammar
const [value, setValue] = useState(0)
This syntax is ES6’s data structure, where the first value of the array declares the state and the second value is the state-changing function.
Each frame has its own state
In my understanding, the closure method is adopted to realize the independent state of each frame.
function Example() {
const [val, setVal] = useState(0)
const timeoutFn = () = > {
setTimeout(() = > {
// The value obtained is the state of the button clicked, not the latest state
console.log(val)
}, 1000)}return (
<>
<p>{val}</p>
<button onClick={()= >setVal(val+1)}>+</button>
<button onClick={timeoutFn}>alertNumber</button>
</>)}Copy the code
When the props or props state of the component is updated, the function component is re-rendered, and each rendering is independent of its props and state, without affecting other renderings.
Initial implementation of useState
let lastState
function useState(initialState) {
lastState = lastState || initialState
function setState(newState) {
lastState = newState
render()
}
return [lastState, setState]
}
// set state's index to 0 every time we render
function render () {
index = 0
ReactDOM.render(<App />.document.getElementById('root'))}Copy the code
It can be seen from the sector code that state and index are strongly correlated. Therefore, setSate can no longer be used under judgment conditions such as if and while, which will result in state update disorder.
- useEffect
grammar
useEffect(() = > {
//handler function...
return () = > {
// clean side effect}},//dep... ] )
Copy the code
UseEffect receives a callback and its dependencies, which are executed when the dependency changes. UseEffect is similar to the didMount, didUpdate, and willUnmount lifecycle functions of the Class component.
Pay attention to the point
- UseEffect is asynchronous and not executed until the component has been rendered
- UseEffect’s callback number can only return a handler that clears the side effect or none
- The function inside useEffect is executed only once if the dependency passed in is an empty array
Implement useEffect
let lastDependencies
function useEffect(callback, dependencies) {
if (lastDependencies) {
letchanged = ! dependencies.every((item, index) = > item == lastDependencies[index])
if (changed) {
callback()
lastDependencies = dependencies
}
} else {
// First render
callback()
lastDependencies = dependencies
}
}
Copy the code
- UseMemo, useCallback
The useMemo and useCallback are used to reduce the number of component updates and optimize component performance.
- UseMemo receives a callback function and its dependencies, and reexecutes the callback function only if the dependency changes.
- UseCallback receives a callback function as well as the dependency and returns the intended version of the function, recalculating the new meorize version only if the dependency changes again
grammar
const memoDate = useMemo(() = > data, [//dep... ] )
const memoCb = useCallback(() = > {/ /... }, [//dep...] )
Copy the code
PureComponent should perform a shouldUpdate comparison to determine if it needs to be updated. We usually use React. Memo for function components. With react hooks, however, the page will be rerendered even if react. memo is used because each render update is independent.
For example, if the name value of a child component is changed, the child component is also re-rendered because the parent component generates a new value each time it is updated (the addAge function changes).
function Parent() {
const [name, setName] = useState('cc')
const [age, setAge] = useState(22)
const addAge = () = > {
setAge(age + 1)}return (
<>
<p>The parent component</p>
<input value={name} onChange={(e)= > setName(e.target.value)} />
<p>age: {age}</p>
<p>-------------------------</p>
<Child addAge={addAge} />
</>)}const Child = memo((props) = > {
const { addAge } = props
console.log('child component update')
return (
<>
<p>Child components</p>
<button onClick={addAge}>click</button>
</>)})Copy the code
Optimize using useCallback
function Parent() {
const [name, setName] = useState('cc')
const [age, setAge] = useState(22)
const addAge = useCallback(() = > {
setAge(age + 1)
}, [age])
return (
<>
<p>The parent component</p>
<input value={name} onChange={(e)= > setName(e.target.value)} />
<p>age: {age}</p>
<p>-------------------------</p>
<Child addAge={addAge} />
</>)}const Child = memo((props) = > {
const { addAge } = props
console.log('child component update')
return (
<>
<p>Child components</p>
<button onClick={addAge}>click</button>
</>)})Copy the code
The memorize function is regenerated only if the useCallback’s dependencies change. So when you change the state of name to addAge it doesn’t change.
Implement useMemo, ueCallBack
Main function: cache variable values, so return the return value of a function
let lastMemo
let lastMemoDependencies
function useMemo(callback, dependencies) {
if (lastMemoDependencies) {
// Render when update
// Determine whether the dependency has changed
letchanged = ! dependencies.every((item, index) = > item == lastMemoDependencies[index])
if (changed) {
lastMemo = callback()
lastMemoDependencies = dependencies
}
} else {
/ / initialization
lastMemo = callback()
lastMemoDependencies = dependencies
}
return lastMemo
}
Copy the code
Implement useCallBack
Main function: cache function
let lastCallback
let lastCallbackDependencies
function useCallback(callback, dependencies) {
if (lastCallbackDependencies) {
// Render when update
// Determine whether the dependency has changed
letchanged = ! dependencies.every((item, index) = > item == lastCallbackDependencies[index])
if (changed) {
lastCallback = callback
lastCallbackDependencies = dependencies
}
} else {
/ / initialization
lastCallback = callback
lastCallbackDependencies = dependencies
}
return lastCallback
}
Copy the code
- useRef
UseRef is similar to react. CreateRef.
const node = useRef(initRef)
Copy the code
UseRef returns a mutable ref object. The current property is initialized as the passed argument (initRef).
It works on the DOM
const node = useRef(null)
<input ref={node}/>
Copy the code
This DOM element can be accessed through the Node.current attribute
It is important to note that the objects created by useRef remain the same throughout the life of the component, which means that every time a function component is re-rendered, the ref object is returned. (Using react.createref, refs are re-created each time the component is re-rendered.)
The realization of the useRef
let lastRef
function useRef(initialRef) {
lastRef = lastRef || initialRef
return {
current: lastRef
}
}
Copy the code
- useReducer
The useReducer is similar to reducer in Redux
grammar
const [state, dispatch] = useReducer(reducer, initState)
Copy the code
The useReducer passes in a calculation function and initializes the state, similar to redux. The state returned by the useReducer can be accessed, and the state can be modified through dispatch.
const initstate = 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 Counter(){
const [state, dispatch] = useReducer(reducer, initstate);
return (
<>
Count: {state.number}
<button onClick={()= > dispatch({type: 'increment'})}>+</button>
<button onClick={()= > dispatch({type: 'decrement'})}>-</button>
</>)}Copy the code
Custom useReducer
let lastState
function useReducer(reducer, initialState) {
lastState = lastState || initialState
function dispatch(action) {
lastState = reducer(lastState, action)
render()
}
return [lastState, dispatch]
}
Copy the code
- useContext
UseContext makes it easier to get the context provided by the upper-layer component
The parent component
import React, { createContext, Children } from 'react'
import Child from './child'
export const MyContext = createContext()
export default function Parent() {
return (
<div>
<p>Parent</p>
<MyContext.Provider value={{name: 'cc', age: 21}} >
<Child />
</MyContext.Provider>
</div>)}Copy the code
Child components
import React, { useContext } from 'react'
import { MyContext } from './parent'
export default function Parent() {
const data = useContext(MyContext) // Get the context provided by the parent component
console.log(data)
return (
<div>
<p>Child</p>
</div>)}Copy the code
Using the step
- The parent component is created and exported
context:export const MyContext = createContext()
- Parent component use
provider
andvalue
Provide value:<MyContext.provide value={{name: 'cc', age:'22'}}/>
- The child component imports the parent component
context: import {MyContext} from './parent'
- Get the value provided by the parent component:
const data = useContext(MyContext)
However, in most cases we do not recommend using the context because it increases the coupling of the component
Use hooks to simplify code. Custom hooks make it easy to reuse logic and get rid of the this problem of class components, but use them with care because they cause closure problems.