A comprehensive explanation of Hooks
Meet Hooks
1.Hook introduction
Hook is a new feature of React 16.8, which allows us to use state
and other React
features (such as life cycle) class
2.Comparison of class and function components
- Let's first think about
class
component has over the functional component? The more common are the following advantages:
Compared | class component | function component |
---|---|---|
state | class component can define its own state to save the internal state of the component | Functional components can’t, because every time the function is called, a new temporary variable is generated. |
Life cycle | class component has its own life cycle, we can complete our own logic in the corresponding life cycle (for example componentDidMount , and the life cycle function will only be executed once) | Before learning hooks functional components, if you send a network request in the function, it means that the network request will be sent again every time you re-render. |
render | class component can re-execute the render function and the life cycle function componentDidUpdate that we want to call again when the state changes only | When a functional component is re-rendered, the entire function will be executed. It seems that there is no place where they can be called only once |
- Therefore, before the
Hook
, we usually writeclass
components for the above situations.
3. Problems with Class components
complex components become incomprehensible:
- When we initially write a
class
component, the logic is often simpler and not very complicated, but as the business increases, ourclass
component will become more and more complex - For example,
componentDidMount
may contain a lot of logic code: including network requests and monitoring of some events (also need to be removedcomponentWillUnmount
- For such
class
it is actually very difficult to split: because their logic is often mixed together, forced splitting will cause over-design and increase the complexity of the code.
- When we initially write a
Incomprehensible class:
- Many people find that learning
ES6
ofclass
is an obstacle toReact
- For example, in
class
, we have to figure outthis
the point of 060af5fa2b119f is, so we need to spend a lot of energy to learnthis
- Although front-end developers must master
this
, it is still very troublesome to deal with
- Many people find that learning
component reuse status is difficult:
- In order to reuse some states in the front, we need to pass high-level components or
render props
- As we learned before
redux
inconnect
orreact-router
inwithRouter
, these higher-order component design purpose is to state the multiplex - Or similar to
Provider、Consumer
to share some state, but whenConsumer
is used multiple times, our code will have a lot of nesting - These codes make it very difficult for us to write and design.
- In order to reuse some states in the front, we need to pass high-level components or
4. The emergence of Hook
Hook
can solve the problems mentioned abovesimple summary
Hooks
- It allows us to use
state
and otherReact
featuresclass
- But we can extend a lot of usage from this to solve the problems we mentioned earlier
- It allows us to use
Usage scenarios of
Hook
- The appearance of Hook can basically replace all
class
components before (except for some very uncommon scenarios) - But if it’s an old project, you don’t need to refactor all the code to Hooks directly, because it is completely backward compatible and you can use it incrementally
- Hook can only be used in function components, and cannot be used in class components or outside of function components.
- The appearance of Hook can basically replace all
Hooks experience
Counter case comparison
Through a counter case, hooks
’s compare the comparison between class
component and functional component combined with 060af5fa2b1422
State Hook
Get to know useState
useState
comes fromreact
and needs toreact
, it is a Hook- Parameters: Initialization value, if not set to
undefined
Return value: array, contains two elements
- Element 1: The value of the current state (the first call is the initial value)
- Element 2: Function to set state value
- Parameters: Initialization value, if not set to
import React, { useState } from 'react'
const [counter, setCounter] = useState(0)
Hook supplement
Hook
isJavaScript
function , this function can help you hook into (hook into
)React State
and life cycle characteristicsTwo additional rules using
Hook
- Only outermost function call Hook. Do not call in loops, conditional judgments or sub-functions
- Hook can only be , the function component Do not call in other JavaScript functions
useState resolution
useState
useState
will help us define astate
variable.useState
is a new method, which is exactly the same function provided byclass
inthis.state
- In general, after the function completes variables will "disappear" while
state
the variables areReact
reserved. useState
receives a unique parameter, which is used as the initial value when the component is called for the first timeuseState
is an array, which can be assigned via [ Assignment of Array]( 160af5fa2b1839 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
) To use
FAQ: Why is it called
useState
and notcreateState
?- "Create" may not be very accurate, because state is only created when the component is first rendered
- The next time we re-render,
useState
return to our currentstate
If a new variable is created every time, it is not "state"
- It is also
Hook
name always touse
a reason for the beginning of the
- It is also
<details>
<summary>useState process analysis</summary>
<img src="https://gitee.com/xmkm/cloudPic/raw/master/img/20201028124155.png" alt="useState process" />
</details>
useState supplement
- The
useState
function can be passed a function
const [counter, setCounter] = useState(() => 10)
- The
setState
function can also be passed a function
// 进行累加都会生效
setCounter(prevState => prevState + 10)
Effect Hook
Meet Effect Hook
useEffect
is an Effect Hook, which adds the ability to manipulate side effects to functional components.It has the same purpose as
componentDidMount
,componentDidUpdate
andcomponentWillUnmount
in the class component is merged into one API
Effect Hook
allows you to complete some functions similar to the life cycle ofclass
in- In fact, similar to network requests, manual updates of
DOM
, and monitoring of some events are all side effects ofReact
updateDOM
Side Effects
) Hook
that completes these functions is calledEffect Hook
import React, { useEffect } from 'react'
useEffect(() => {
console.log('useEffect被执行了')
})
Analysis of useEffect
- role: through the Hook of
useEffect
React
that some operations need to be performed after rendering - parameters:
useEffect
requires us to pass in a callback function, this function will beReact
performs the updateDOM
- execution timing: after the first rendering, or every time the status is updated, this callback function will be executed
Need to clear Effect
In the
class
component, we need to perform clearcomponentWillUnmount
for some side-effect codes.
- For example, our previous event bus or
Redux
manually callsubscribe
- Need to cancel the subscription
componentWillUnmount
Effect Hook
doescomponentWillUnmount
simulate 060af5fa2b1eac?
useEffect
can itself have a return value, which is another <font color='red'>callback function B</font>
type EffectCallback = () => (void | (() => void | undefined))
import React, { useEffect, useState } from 'react'
useEffect(() => {
console.log('订阅一些事件')
// 取消订阅
return () => {
console.log('取消订阅')
}
})
Why return a function
Effect
- This is
Effect
optional clearance mechanisms . Eacheffect
can return a clear function - This way, the logic of adding and removing subscriptions can be put together
- They are all part of
effect
- This is
React
when to clearEffect
- React will perform a cleanup operation when the component is updated and uninstalled
- As you learned before,
effect
will be executed every time you render
Use multiple effects
One of the purposes of usingHook
solve theclass
life cycle in 160af5fa2b20fc 060af5fa2b20f7, often puts a lot of logic together :Such as network requests, event monitoring, manual modification of the DOM, these are often placed in
componentDidMount
- Using
Effect Hook
, we can separate them into differentuseEffect
:
useEffect(() => {
console.log('修改DOM')
})
useEffect(() => {
console.log('订阅事件')
}, [])
useEffect(() => {
console.log('网络请求')
}, [])
according to the purpose of the code, instead of like life cycle functions:
React
will in the component in the order ofeffect
effect
Effect performance optimization
By default,
useEffect
will be re-executed every time it is rendered, but this will cause two problems:
- Some codes we just want to execute once, similar to
componentDidMount
andcomponentWillUnmount
; (such as network requests, subscriptions, and unsubscriptions)- In addition, multiple executions will also cause certain performance problems
How do we decide when
useEffect
should be executed and when it should not be executed?useEffect
actually has two parameters- Parameter 1: The callback function to be executed
- Parameter 2: The
useEffect
depends onstate
to change, only then re-execute the callback (affected by whom)
However, if a function does not want to rely on any content , we can also pass in an empty array
[]
- Then the two callback functions here correspond to the life cycle functions of
componentDidMount
andcomponentWillUnmount
- Then the two callback functions here correspond to the life cycle functions of
useContext
Why use useContext?
In the previous development: There are two ways we want to use the shared
Context
- Class components can be obtained by:
class name.contextType = myContext method, get
context
- A plurality
Context
or functional component throughMyContext.consumer
sharingcontext
- Class components can be obtained by:
But multiple
Context
sharing makes large number of nestedContext Hook
allows us to directly obtain aContext
valueHook
Use of useContext
import React, { useContext } from 'react'
import { Theme, User } from '../App'
export default function UseContextDemo() {
// useContext使用
const user = useContext(User)
const theme = useContext(Theme)
console.log(user, theme)
return (
<div>
<h3>UseContextDemo</h3>
</div>
)
}
Precautions:
<MyContext.Provider>
upper layer of the component is updated, theHook
will trigger a re-rendering and use the latestcontext value
valueMyContext provider
useReducer
Introduction to useRducer
Many people seeuseReducer
first reaction should beredux
an alternative, not really.
useReducer
just an alternative touseState
- Be used: In some scenarios, if
state
the processing logic is more complex, we canuseReducer
be split its - Or when the modified
state
needs to depend on the previousstate
, it can also be used
useReducer use
// reducer.js
export default function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 }
case 'decrement':
return { ...state, count: state.count - 1 }
default:
return state
}
}
// home.js
import reducer from './reducer'
export default function Home() {
// 参数1: reducer 参数2: 初始state
const [state, setState] = useReducer(reducer, { count: 0 })
return (
<div>
<h2>当前计数: {state.count}</h2>
<button onClick={e => setState({ type: 'increment' })}>+</button>
<button onClick={e => setState({ type: 'decrement' })}>-</button>
</div>
)
}
- data will not share , they just use same
counterReducer
function only - So,
useReducer
isuseState
an alternative to, and can not replaceRedux
useCallback
Introduction to useCallback
- Imagine: when you update the name attribute, all event processing functions after re-calling render are redefined, which is a waste of performance
- Solution: When depends on the attribute does not change , when you do not want to update the render, redefines the event function
useCallback use
useCallBack
timing goal is to optimize performanceHow to optimize performance?
useCallBack
will return a functionmemoized
(remembered) value- In the case of constant dependency, when multiple definitions are made, the return value is the same
const increment2 = useCallback(() => {
console.log('increment2被调用了')
setCount(count + 1)
}, [count])
useCallback usage scenarios
- Scenario: When passing a function in a component, to the child element callback function , use
useCallback
to process the function
import React, { useState, useCallback, memo } from 'react'
const JMButton = memo(props => {
console.log('HYButton重新渲染: ', props.title)
return <button onClick={props.increment}>JMButton+1</button>
})
export default function CallBackHomeDemo2() {
// useCallback: 希望更新父组件的state时,子组件不被render渲染
// 1.使用memo包裹子组件进行性能优化,子组件没有依赖的props或state没有修改,不会进行render
// 2.一个疑问: 为什么 btn1 还是被渲染了?
// (1)因为子组件依赖的 increment1 函数,在父组件没有进行缓存(在函数重新render时,increment1被重新定义了)
// (2)而 increment2 函数在父组件中被缓存了,所以memo函数进行性浅层比较时依赖的increment2是一样的所以没有被重新render渲染
// 3.useCallback在什么时候使用?
// 场景: 在将一个组件中的函数, 传递给子元素进行回调使用时, 使用useCallback对函数进行处理.
console.log('CallBackHomeDemo2重新渲染')
const [count, setCount] = useState(0)
const [show, setShow] = useState(true)
const increment1 = () => {
console.log('increment1被调用了')
setCount(count + 1)
}
const increment2 = useCallback(() => {
console.log('increment2被调用了')
setCount(count + 1)
}, [count])
return (
<div>
<h2>CallBackHomeDemo: {count}</h2>
<JMButton increment={increment1} title="btn1" />
<JMButton increment={increment2} title="btn2" />
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
useMemo
Introduction to useMemo
useMemo
actual purpose of 060af5fa2b2d9c is also to optimize performance.- How to optimize performance?
useMemo
returns amemoized
(remembered) value;- In depends on unchanged , the returned value is the same when it is defined multiple times;
// 依赖没有改变的话,是不会进行重新定义局部变量的
const info = useMemo(() => {
return { name: 'kobe', age: 18 }
}, [])
useMemo usage scenarios
- Scenario: When passing a function in a component, to the child element local variable , use
useMemo
to process the function
import React, { useState, memo, useMemo } from 'react'
const User = memo(props => {
console.log('User被渲染了')
return (
<h3>
姓名: {props.info.name} 年龄:{props.info.age}
</h3>
)
})
export default function MemoHookDemo2() {
console.log('MemoHookDemo2被渲染了')
// 需求: 在更新父组件的局部变量时,子组件依赖的props或state没有改变不希望被render渲染
// 1.memo包裹子组件
// 2.为什么子组件User还是被渲染了呢?
// (1)因为: 在父组件重新渲染时,会吃会重新创建info对象,memo在对比时会发现两次创建的info对象不同,会重新render渲染
// const info = { name: 'kobe', age: 18 }
// (2)解决: 使用useMemo
const info = useMemo(() => {
return { name: 'kobe', age: 18 }
}, [])
const [show, setShow] = useState(true)
return (
<div>
<User info={info} />
<button onClick={e => setShow(!show)}>切换</button>
</div>
)
}
useRef
Introduction to useRef
- Introduction:
useRef
returns aref
object, the returnedref
object remains unchanged throughout the life cycle of the component The most commonly used
ref
are two scenarios:- Scenario 1: Introduce
DOM
(or component, need to beclass
component) element - Scenario 2: Save a piece of data, this object can be preserved throughout its life cycle
- Scenario 1: Introduce
const refContainer = useRef (initialvalue);
Reference DOM
import React, { useRef } from 'react'
class ChildCpn extends React.Component {
render() {
return <div>ChildCpn</div>
}
}
export default function RefHookDemo01() {
const titleRef = useRef()
const cpnRef = useRef()
function changeDOM() {
// 修改DOM
titleRef.current.innerHTML = 'hello world
console.log(cpnRef.current)
}
return (
<div>
{/* 1.修改DOM元素 */}
------<h2 ref={titleRef}>RefHookDemo01</h2>------
{/* 2.获取class组件 ✔ */}
<ChildCpn ref={cpnRef} />
<button onClick={changeDOM}>修改DOM</button>
</div>
)
}
Use ref to save the last value
import React, { useEffect, useRef, useState } from 'react'
export default function RefHookDemo02() {
// 需求: 使用ref保存上一次的某一个值
const [count, setCount] = useState(0)
// 将上一次的count进行保存,在count发生改变时,重新保存count
// 为什么: 在点击button时,增加count时,会调用useEffect函数,渲染DOM后,会重新将上一次的值进行保存,使用ref保存上一次的某一个值不会触发render
const numRef = useRef(count)
useEffect(() => {
numRef.current = count
}, [count])
return (
<div>
<h3>count上一次的值: {numRef.current}</h3>
<h3>count这一次的值 {count}</h3>
<button onClick={e => setCount(count + 10)}>+10</button>
</div>
)
}
useImperativeHandle
useImperativeHandle introduced
Let's first review the combination of
ref
andforwardRef
- By
forwardRef
canref
forwarded to sub-assemblies - The child component gets the
ref
created by the parent component and binds it to one of its own elements
- By
import React, { useRef, forwardRef } from 'react'
// forwardRef可以将ref转发给子组件
const JMInput = forwardRef((props, ref) => {
return <input type="text" ref={ref} />
})
export default function ForwardDemo() {
// forward用于获取函数式组件DOM元素
const inputRef = useRef()
const getFocus = () => {
inputRef.current.focus()
}
return (
<div>
<button onClick={getFocus}>聚焦</button>
<JMInput ref={inputRef} />
</div>
)
}
forwardRef
itself is not a problem, but we areDOM
of the child component to the parent component:- The problem directly exposed to the parent component is that some situations are uncontrollable
- The parent component can perform any operation after
DOM
- We just hope that the parent component can operate
focus
, others do not want it to operate other methods at will
Introduction to useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
By
useImperativeHandle
can expose only a specific operation- By
useImperativeHandle
Hook, the parent assembly incomingref
anduseImperativeHandle
second parameter is returned to the object bound together - So the parent component, call
inputRef.current
when, in fact, return back object
- By
A simple summary of the use of
useImperativeHandle
- Role: Reduce the
DOM
element attributes that are exposed to the parent component, and only expose theDOM
method that the parent component needs to use - Parameter 1: ref attribute passed by the parent component
- Parameter 2: Return an object for the parent component to call the method in the object
ref.current
- Role: Reduce the
import React, { useRef, forwardRef, useImperativeHandle } from 'react'
const JMInput = forwardRef((props, ref) => {
const inputRef = useRef()
// 作用: 减少父组件获取的DOM元素属性,只暴露给父组件需要用到的DOM方法
// 参数1: 父组件传递的ref属性
// 参数2: 返回一个对象,父组件通过ref.current调用对象中方法
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
},
}))
return <input type="text" ref={inputRef} />
})
export default function ImperativeHandleDemo() {
// useImperativeHandle 主要作用:用于减少父组件中通过forward+useRef获取子组件DOM元素暴露的属性过多
// 为什么使用: 因为使用forward+useRef获取子函数式组件DOM时,获取到的dom属性暴露的太多了
// 解决: 使用uesImperativeHandle解决,在子函数式组件中定义父组件需要进行DOM操作,减少获取DOM暴露的属性过多
const inputRef = useRef()
return (
<div>
<button onClick={() => inputRef.current.focus()}>聚焦</button>
<JMInput ref={inputRef} />
</div>
)
}
useLayoutEffect
Introduction to useLayoutEffect
useLayoutEffect
looksuseEffect
, in fact they have only one difference:useEffect
will update the rendered content toDOM
on then execute, and will not block theDOM
updateuseLayoutEffect
will render content updates toDOM
on before execution, will blockDOM
update
- Usage scenarios: if we want to after performing certain operations and then
DOM
, then the operation should be putuseLayoutEffect
, attention will block page rendering
useLayoutEffect use
import React, { useEffect, useLayoutEffect, useState } from 'react'
export default function LayoutEffectCountChange() {
const [count, setCount] = useState(10)
// 主要作用: 执行某些操作之后再执行DOM渲染,会阻塞页面渲染
useLayoutEffect(() => {
if (count === 0) {
setCount(Math.random())
}
}, [count])
return (
<div>
<h2>数字: {count}</h2>
<button onClick={e => setCount(0)}>change 0 state for count</button>
</div>
)
}
Custom Hook
Custom Hook introduction
CustomHook
is essentially just a function code logic extraction , strictly speaking, it is not a feature of React itself.Usage scenario: You can extract the repeated logic of the component into a reusable function
Custom Hook use
- How to customize: Custom Hook is a function whose name starts with "
use
", and other Hooks can be called inside the function. Hook
defined below is: When the component is created and uninstall , it will be printed to the console "current component life cycle information"
import React, { useEffect } from 'react'
// 函数前面添加use 成为自定义Hook,可以使用Hook特性
function useLifeFlow(name) {
useEffect(() => {
console.log(`${name}被创建`)
return () => {
console.log(`${name}被卸载`)
}
}, [])
}
function Home() {
useLifeFlow('Home')
return <h2>Home</h2>
}
function Profile() {
useLifeFlow('Profile')
return <h2>Profile</h2>
}
export default function CustomLifeHookDemo() {
useLifeFlow('CustomLiftHookDemo')
return (
<div>
<h2>CustomLiftHookDemo</h2>
<Home />
<Profile />
</div>
)
}
Custom Hook scene
Requirement 1: Context sharing
// 自定义Hook 函数前添加 use (共享多个context,将多个context进行封装)
export default function useUserContext() {
// 获取祖先组件或父组件提供 contetx provide value
const user = useContext(UserContext)
const token = useContext(TokenContext)
// 将同一类型的 contetx provide value 返回
return [user, token]
}
Requirement 2: Get the mouse scroll position
export default function useScrollPosition() {
const [scrollY, setScrollY] = useState(0)
// 挂载完DOM后,注册scroll事件
useEffect(() => {
const handleScroll = () => {
setScrollY(window.scrollY)
}
document.addEventListener('scroll', handleScroll)
// 组件卸载后移除scroll事件
return () => {
document.removeEventListener('scroll', handleScroll)
}
}, [])
return scrollY
}
Requirement 3: localStorage data storage
function useLocalStorage(key) {
const [data, setData] = useState(() => {
const data = JSON.parse(window.localStorage.getItem(key))
return data
})
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(data))
}, [data])
return [data, setData]
}
export default useLocalStorage
Redux Hook
useDispatch
- Using
useDispatch
can make you no longer need to define the dependentdispatch
action
function in the component dispatch
directly in the component to distributeaction
function JMRecommend(props) {
// redux Hook 组件和redux关联: 获取数据和进行操作
const dispatch = useDispatch()
useEffect(() => {
dispatch(getTopBannersAction())
}, [dispatch])
return (
<div>
<h2>JMRecommend</h2>
</div>
)
}
export default memo(JMRecommend)
useSelector
- After using
useSelector
, there is no need to define dependentstate
in the component, and directly useuseSelector
in the component. The parameter of the transfer function isstate
- The function returns an object, in which the dependent
state
function JMRecommend(props) {
// redux Hook 组件和redux关联: 获取数据和进行操作
const { topBanners } = useSelector(state => ({
topBanners: state.recommend.topBanners,
}))
return (
<div>
<h2>JMRecommend</h2>
<h3>{topBanners.length}</h3>
</div>
)
}
export default memo(JMRecommend)
useSelector performance optimization
- Both components rely on and use
state in redux. One component changes
state
other component will be re-rendered, which is normal useSelector
question :- As long as
reducer
instate
changes, no matter whether the component depends onstate
, it will be re-rendered
- As long as
<details>
<summary>useSelector problem (icon)</summary><img src="https://mingcloudpic.oss-cn-beijing.aliyuncs.com/img/20201028123347.gif" alt="render" style="zoom:80%;" />
</details>
The problem with useSelector?
Why does useSelector have this problem?
- Because
useSelector
used, decides whether to re-render the component beforeA reference comparison will be performed: a reference comparison will be performed with the
state
object returned by the previous function (third-class operator)
- Because every time the function is called, the created object is a brand new object
- So every time the state
store changes, regardless of whether the current component depends on the
state
component, it will be re-rendered
useSelector optimization
useSelector
Optimization:useSelector
of a second parameterShallowEqual
ShallowEqual
Function: Compare a shallow comparison with the object returned by theuseSelector
<details>
<summary>useSelector performance optimization (icon)</summary><img src="https://mingcloudpic.oss-cn-beijing.aliyuncs.com/img/20201028123443.gif" alt="render" style="zoom:80%;" />
</details>
import React from 'react'
import { shallowEqual, useSelector } from 'react-redux'
export default function About(props) {
console.log('About 组件被重新渲染')
// 使用shallowEqual解决useSelector问题
const { banners, recommends } = useSelector(state => ({
banners: state.banners,
recommends: state.recommends
}), shallowEqual)
return (
<div>
<h1>About</h1>
<h4>组件没有依赖count: 但还是被重新渲染了</h4>
<h4>使用shallowEqual解决useSelector渲染问题</h4>
</div>
)
}
- In the future, as long as the current component does not depend on the changed
state
do not want to re-render, useuseSelector
combination withShallowEqual
- Note:
connect
function does not have this problem
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。