0%

react中的hooks

为什么需要Hook

不编写class的情况下使用state以及其他的React特性

class组件

class组件的优势

  • class组件可以定义自己的state,用来保存组件自己内部的状态
    • 函数式组件不可以,因为函数每次调用都会产生新的临时变量
  • class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑
    • 比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次
    • 函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求
  • class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate
    • 函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次

class组件存在的问题

  • 复杂组件变得难以理解

    • 我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂
    • 比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在
      componentWillUnmount中移除)
    • 而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度
  • 难以理解的class

    • 在class中,我们必须搞清楚this的指向到底是谁,所以需要花很多的精力去学习this
  • 组件复用状态很难
    • 状态的复用我们需要通过高阶组件
    • 像我们之前学习的redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用
    • 或者类似于Provider、Consumer来共享一些状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套

Hook的作用

Hook可以解决上述class和函数组件存在的问题

它可以让我们在不编写class的情况下使用state以及其他的React特性

Hook的使用场景

  • Hook的出现基本可以代替我们之前所有使用class组件的地方
  • 但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它
  • Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用

Hooks的使用

Hooks只能在函数组件中使用

为什么Hooks中的函数都叫use,不叫create呢?

  • Hooks的一大作用是保存组件的状态,在下一次渲染时,返回当前的state
  • 而create的意思是,每次渲染都重新创建,那么就与state只在组件初次渲染时被创建相反

useState的使用

useState主要在函数组件中定义state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { memo, useState } from 'react'

function App() {
const [count, setCounter] = useState(0)
console.log("count", count)
console.log("setCounter", setCounter)
return (
<div>
<h2>count:{count}</h2>
{/**
* 注意:onClick传入的是一个函数,setCounter(count+1)是一个函数的调用,所以这里只能使用箭头函数,在函数调用里调用setCounter
*/}
{/* <button onClick={setCounter(count+1)}>+1</button> */}
<button onClick={()=>{setCounter(count+1)}}>+1</button>
</div>
)
}
export default memo(App)
  • useState传入的参数是state中变量的初始值,不传则为默认值undefined
  • useState返回的是一个数组,其中数组中的第一个值是state变量的值,第二个值是设置状态值的函数

  • 只能在最外层调用Hook,不能在循环、条件判断或者子函数中调用

  • 只能在React的函数组件中调用Hook,不能在其他JavaScript函数中调用

useState如何实现状态

useState原理

Effect Hook

  • Effect Hook可以在函数组件中实现类似于生命周期的功能
  • 网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(不是页面渲染)
  • 对于完成这些功能的Hook被称之为 Effect Hook

Effect Hook的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { memo, useEffect, useState } from 'react'

function App() {
const [count, setCounter] = useState(0)
useEffect(()=>{
console.log("监听redux中的数据变换,监听eventBus中的事件")
})
return (
<div>
<h2>count:{count}</h2>
<button onClick={()=>{setCounter(count+1)}}>+1</button>
</div>
)
}
export default memo(App)
  • useEffect传入的参数为一个回调函数,这个回调函数在更新DOM操作之后,就会执行
  • 但是在默认情况下,无论第一次渲染,还是在上面更改count更新后的渲染,在渲染结束后都会执行这个回调函数

  • 由于每次渲染都会执行useEffect中的函数,那么每次都会添加监听,最后会添加若干监听,因此需要在函数组件卸载或渲染前卸载掉之前的监听

Effect Hook的清除

  • 在class组件中,某些副作用的代码,可以在componentWillUnmount中进行清除,比如在这个函数中清除Redux的订阅
  • useEffect通过传入的回调函数1的返回值函数2,在返回值函数2中做清除操作

    • 这样做可以将添加和移除订阅的逻辑放在一起
    • 同属于一个effect的一部分,更好管理
  • React何时清除useEffect

    • 在组件更新和卸载时执行清除操作(装载时不会清除)

多个Effect的使用

  • 使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题

    • 比如网络请求、事件监听、手动修改DOM,这些往往都会放在componentDidMount中
  • 使用Effect Hook,我们可以将它们分离到不同的useEffect中

    • Hook 允许我们按照代码的用途分离它们, 而不是像生命周期函数那样
    • React 将按照 effect 声明的顺序依次调用组件中的每一个 effect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { memo, useEffect, useState } from 'react'

function App() {
const [count, setCounter] = useState(0)
useEffect(()=>{
console.log("监听redux中的数据变换,监听eventBus中的事件")
return ()=>{
console.log("清除监听redux中的数据变换,清除监听eventBus中的事件")
}
})
useEffect(()=>{
console.log("获取网络请求")
})
return (
<div>
<h2>count:{count}</h2>
<button onClick={()=>{setCounter(count+1)}}>+1</button>
</div>
)
}
export default memo(App)

Effect的性能优化

  • 默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问题

    • 某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网络请求、订阅和取消订阅)
    • 另外,多次执行也会导致一定的性能问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import React, { memo, useEffect, useState } from 'react'

    function App() {
    const [count, setCounter] = useState(0)
    const [message, setMessage] = useState("Hello!")
    useEffect(()=>{
    console.log("监听redux中的数据变换,监听eventBus中的事件")
    return ()=>{
    console.log("清除监听redux中的数据变换,清除监听eventBus中的事件")
    }
    })
    return (
    <div>
    <h2>count:{count}</h2>
    <h2>message:{message}</h2>
    <button onClick={()=>{console.log("加count");setCounter(count+1)}}>+1</button>
    <button onClick={()=>{console.log("修改message");setMessage("你哈个锤子")}}>修改message</button>
    </div>
    )
    }
    export default memo(App)

    image-20230403201741719

  • useEffect的实际上有两个参数

    • 参数一:执行的回调函数
    • 参数二:该useEffect在哪些state发生变化时,才重新执行;(受谁的影响)——如果不希望受到任何依赖的影响,则传入[],这时候仅会在创建和销毁时分别执行对应回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { memo, useEffect, useState } from 'react'

function App() {
const [count, setCounter] = useState(0)
const [message, setMessage] = useState("Hello!")
useEffect(()=>{
console.log("监听redux中的数据变换,监听eventBus中的事件")
return ()=>{
console.log("清除监听redux中的数据变换,清除监听eventBus中的事件")
}
},[count])
return (
<div>
<h2>count:{count}</h2>
<h2>message:{message}</h2>
<button onClick={()=>{console.log("加count");setCounter(count+1)}}>+1</button>
<button onClick={()=>{console.log("修改message");setMessage("你哈个锤子")}}>修改message</button>
</div>
)
}
export default memo(App)

image-20230403201907764

那么这里的两个回调函数分别对应的就是componentDidMount和componentWillUnmount生命周期函数了

useContext的使用

获取Context的方式:

  • 类组件可以通过 类名.contextType = MyContext方式,在类中获取context
  • 多个Context或者在函数式组件中通过 MyContext.Consumer 方式共享context

但是多个Context共享时会存在大量的嵌套——Context Hook允许我们通过Hook来直接获取某个Context的值

但是Provider依然会有嵌套

Provider代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { HashRouter } from 'react-router-dom';
import { ColorContext, BgrContext } from './Context';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(

<ColorContext.Provider value={{color:"red"}}>
<BgrContext.Provider value={{fontSize:36}}>
<HashRouter>
<App/>
</HashRouter>
</BgrContext.Provider>
</ColorContext.Provider>
);

useContext代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { memo, useContext, useEffect, useState } from 'react'
import { ColorContext, BgrContext } from './Context'


function App() {
const colorContext = useContext(ColorContext)
const bgrContext = useContext(BgrContext)
return (
<div>
<h2 style={{color:colorContext.color,fontSize:bgrContext.fontSize}}>哈哈哈</h2>
</div>
)
}
export default memo(App)

image-20230403204016535

useReducer的使用

useReducer仅仅是useState的一种替代方案——与Redux关系不大

  • 如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分
  • 或者这次修改的state需要依赖之前的state时,也可以使用
  • 数据是不会共享的,它们只是使用了相同的counterReducer的函数而已
  • useReducer只是useState的一种替代品,并不能替代Redux
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { memo, useReducer } from 'react'

const counterReducer = function(state, action){
switch(action.type){
case "increment":
return {...state, counter:state.counter+1}
case "decrement":
return {...state, counter:state.counter-1}
case "addNum":
return {...state, counter:state.counter+action.num}
case "decNum":
return {...state, counter:state.counter-action.num}
}
}
function App() {
const [state, dispatch] = useReducer(counterReducer,{message:"Hello!",counter:100})
return (
<div>
<h2>当前计数:{state.counter}</h2>
<button onClick={()=>{dispatch({type:"increment"})}}>+1</button>
<button onClick={()=>{dispatch({type:"decrement"})}}>-1</button>
<button onClick={()=>{dispatch({type:"addNum", num:5})}}>+5</button>
<button onClick={()=>{dispatch({type:"decNum", num:10})}}>-10</button>
</div>
)
}
export default memo(App)

useReducer的参数与返回值:

  • 第一个参数:reducer函数
    • reducer函数的参数一:上一次的state/useReducer的第二个参数
    • reducer函数的参数二:dispatch所传入的参数
    • reducer的返回值:新的状态(…state是为了保障除了这次改变的其余状态依然在新的状态中)
  • 第二个参数:state初始值
  • 第一个返回值:更新后的state值
  • 第二个返回值:用于发起改变state的函数

useCallback的使用

useCallback实际的目的是为了进行性能的优化

  • 函数式组件在每次刷新时都会重新执行函数,那么里面的函数就会重新定义,虽然刷新后,原来定义的函数会被回收,但每次都定义也会影响性能,同时会生成新的foo
  • useCallback就可以实现当满足某些条件时重新定义函数,但得到相同的foo,而如果有子组件依赖这个foo,相同的foo不会引起子组件的刷新

父组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { memo, useCallback, useState } from 'react'
import ChildComponent from './component/ChildComponent'

function App() {
const [count, setCount] = useState(100)
const [message, setMessage] = useState(22)
const changeCount = ()=>{
console.log("修改count")
setCount(count+1)
}
const changeMessage = ()=>{
console.log("修改message")
setMessage(Math.round())
}
return (
<div>
<h2>当前计数:{count}</h2>
<h2>Message:{message}</h2>
<ChildComponent changeMessage={changeMessage}/>
<button onClick={changeCount}>+1</button>
</div>
)
}
export default memo(App)

子组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
import React, { memo } from 'react'

function ChildComponent(props) {
const {changeMessage} = props
console.log("子组件刷新")
return (
<div>
<button onClick={changeMessage}>子组件按钮修改Message</button>
</div>
)
}
export default memo(ChildComponent)

image-20230403211929250

可以看出每次修改count都会刷新不依赖count子组件,会造成性能浪费

useCallback的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { memo, useCallback, useState } from 'react'
import ChildComponent from './component/ChildComponent'

function App() {
const [count, setCount] = useState(100)
const [message, setMessage] = useState(22)
const changeCount = useCallback(()=>{
console.log("修改count")
setCount(count+1)
},[count])
const changeMessage = useCallback(()=>{
console.log("修改message")
setMessage(Math.round())
},[])
return (
<div>
<h2>当前计数:{count}</h2>
<h2>Message:{message}</h2>
<ChildComponent changeMessage={changeMessage}/>
<button onClick={changeCount}>+1</button>
</div>
)
}
export default memo(App)

image-20230403212738937

可以看出点击更改count后,子组件并未刷新

useCallback的参数与返回值:

  • 参数一:需要处理的函数
  • 参数二:该函数改变依赖的状态,如果该依赖的状态不变,就返回相同的函数,相同的函数不会引起依赖该函数子组件的刷新
  • 返回值:返回一个函数的 memoized(记忆的) 值

使用useCallback的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存

  • 并不是不会新定义函数,新定义函数是一定的
  • 只针对子组件的渲染次数做优化,在本组件层面是没有优化的

useMemo的使用

useMemo也是为了进行性能的优化:

  • 同样是返回一个memoized(记忆的)值
  • 在依赖不变的情况下,多次定义的时候,返回的值是相同的
  • useCallback是在依赖不变的情况下,多次定义的时候,返回的函数是相同的
  • useMemo针对变量进行优化,useCallback针对函数进行优化

useMemo的使用案例:

  • 进行大量的计算操作,是否有必须要每次渲染时都重新计算
  • 对子组件传递相同内容的对象时,使用useMemo进行性能的优化

父组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { memo, useCallback, useState } from 'react'
import ChildComponent from './component/ChildComponent'

function App() {
const [count, setCount] = useState(100)
function changeCount(){
console.log("修改count")
setCount(count+1)
}
function foo(information){
console.log("生成information")
return {...information,message:"helloWorld"}
}
const information = foo({message:message})
return (
<div>
<h2>当前计数:{count}</h2>
<h2>Message:{information.message}</h2>
<ChildComponent information={information}/>
<button onClick={changeCount}>+1</button>
</div>
)
}
export default memo(App)

子组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
import React, { memo } from 'react'

function ChildComponent(props) {
const {information} = props
console.log("子组件刷新")
return (
<div>
<h3>子组件:{information.message}</h3>
</div>
)
}
export default memo(ChildComponent)

image-20230404094727924

可以看出:

  • 每次更新count后,message是不变的,那么information,也就不必要每次都去运行foo函数,当foo函数逻辑赋值时会引起性能浪费
  • 子页面仅依赖于message,这里明显每次都是得到的information值是一样的,但由于是不同的对象,子组件每次也会刷新

useMemo的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React, { memo, useMemo, useState } from 'react'
import ChildComponent from './component/ChildComponent'

function useFoo(message){
const res = useMemo((message)=>{
console.log("生成information")
return {message:message+"修改后"}
},[message])
return res
}
function App() {
const [count, setCount] = useState(100)
const [message, setMessage] = useState("helloWorld")
function changeCount(){
console.log("修改count")
setCount(count+1)
}
let information = useFoo(message)
return (
<div>
<h2>当前计数:{count}</h2>
<h2>Message:{message}</h2>
<ChildComponent information={information}/>
<button onClick={changeCount}>+1</button>
</div>
)
}
export default memo(App)

image-20230404101149326

可以看出:

修改count后,依赖于message的函数不再重新执行,子组件也不再刷新

注意:

  • useMemo这种Hook函数,只能在函数组件和hook函数中使用,自定义hook函数,以use开头,use后的第一个字母为大写
  • useMemo不会传递参数,因为useMemo是由React调用的,我们不知道其在上面时候调用,也就无法传参数进行
  • useMemo的参数与返回值
    • 参数一:useMemo判断是否执行的函数
    • 参数二:useMemo依赖的值,如果这个值没变,就不会执行传入的函数,直接返回上一次的计算值
    • 返回值:返回一个带记忆的值

useRef的使用

useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变

useRef基本使用

  • 用法一:引入DOM(或者组件,但是需要是class组件)元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { memo, useRef} from 'react'
function App() {
const inputRef = useRef()
const buttonClick = ()=>{
console.log(inputRef.current)
inputRef.current.focus()
}
return (
<div>
<input type="text" ref={inputRef}/>
<button onClick={buttonClick}>修改</button>
</div>
)
}
export default memo(App)
  • 用法二:保存一个数据,这个对象在整个生命周期中可以保存不变(解决闭包陷阱)
    • 由于函数里的count是获取上层作用域里的,也就是闭包,那么无论页面刷新多少次,函数里的count都是函数定义是的count 0,因此无论多少次点击得到的结果都是1(当然这里可以设置依赖count——仅颜色useRef的作用,所以没有添加)
    • 而使用inputRef所得到的对象每次本身就是一样的,那么有没有闭包陷阱也就无所谓,因为我们本身就是要获取相同的值,而通过修改inptRef这个对象里面的值,从而获得相同的对象不同的值,解决闭包陷阱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { memo, useCallback, useRef, useState} from 'react'
function App() {
const [count,setCount] = useState(0)
const buttonClick = useCallback(function(){
console.log("+1",count+1)
setCount(count+1)
},[])
const inputRef = useRef()
inputRef.count = count
const buttonClick1 = useCallback(function(){
console.log("-1",inputRef.count-1)
setCount(inputRef.count-1)
},[])
return (
<div>
<h2>count:{count}</h2>
<button onClick={buttonClick}>+1</button>
<button onClick={buttonClick1}>-1</button>
</div>
)
}
export default memo(App)

image-20230404105354101

父组件传ref给子组件

  • 方式一:作为参数放到props中传递过去——参数名不能使用ref
  • 方式二:利用forwardRef传递给子组件

父组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { memo, useRef, useState} from 'react'
import ChildComponent from './component/ChildComponent'
function App() {
const inputRef = useRef()
const buttonClick = ()=>{
console.log(inputRef.current)
inputRef.current.focus()
inputRef.current.value = ""
}
return (
<div>
<ChildComponent ref = {inputRef}/>
<button onClick={buttonClick}>showref</button>
</div>
)
}
export default memo(App)

子组件代码:

1
2
3
4
5
6
7
8
9
10
import React, { forwardRef, memo } from 'react'

function ChildComponent(props, ref) {
return (
<div>
<input type="text" ref={ref}/>
</div>
)
}
export default memo(forwardRef(ChildComponent))

注:要使用ref参数,子组件需用forwardRef包裹,且要使用memo的话,memo在forwardRef外层

useImperativeHandle的使用

  • 上述通过父组件中的ref传递给子组件中的元素,可以在父组件中获得子组件元素,对子组件元素进行操作
  • 将整个元素给到父组件,就容易在父组件中过渡使用,从而在某种情况下产生某种bug,如上述代码,点击showref会在聚焦子组件input的同时,将其值修改为空
  • useImperativeHandle就是为了解决样的问题,不直接把子组件元素给到父组件,而是把子组件元素的某些操作给到父组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { forwardRef, memo, useImperativeHandle, useRef } from 'react'

function ChildComponent(props, ref) {
const inputRef = useRef()
useImperativeHandle(ref, ()=>{
return {
focus:()=>{
inputRef.current.focus()
}
}
})
return (
<div>
<input type="text" ref={inputRef}/>
</div>
)
}
export default memo(forwardRef(ChildComponent))

useImperativeHandle的基本使用

  • 参数一:父组件传给子组件的ref
  • 参数二:回调函数,函数返回结果是传递给父组件的属性值对象,如这里只传了focus函数,那么父组件中就只有focus属性生效,其余属性无效

useLayoutEffect使用

useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:

  • useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新
  • useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新

image-20230404143022932

useEffect与useLayoutEffect执行顺序演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { memo, useEffect, useLayoutEffect} from 'react'
function App() {
useEffect(()=>{
console.log("useEffect")
})
useLayoutEffect(()=>{
console.log("useLayoutEffect")
})
console.log("render app")
return (
<div>
<h2>哈哈哈</h2>
</div>
)
}
export default memo(App)

image-20230404150134192

注:先”render app”,等数据更新后,渲染到DOM前执行useLayoutEffect,渲染结束后执行useEffect

useLayoutEffect的作用

在页面即将刷新错误数据前及时拦截

使用useEffect代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { memo, useEffect, useState} from 'react'
function App() {
const [count, setCount] = useState(10)
useEffect(()=>{
if(count===0){
setCount(10)
}
console.log("useEffect",count)
}, [count])
console.log("render app")
function buttonClick(){
setCount(count-1)
}
return (
<div>
<h2>count:{count}</h2>
<button onClick={buttonClick}>-1</button>
</div>
)
}
export default memo(App)

动画

使用useLayoutEffect代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { memo, useLayoutEffect, useState} from 'react'
function App() {
const [count, setCount] = useState(10)
useLayoutEffect(()=>{
if(count===0){
setCount(10)
}
console.log("useEffect",count)
}, [count])
console.log("render app")
function buttonClick(){
setCount(count-1)
}
return (
<div>
<h2>count:{count}</h2>
<button onClick={buttonClick}>-1</button>
</div>
)
}
export default memo(App)

动画

自定义Hook

自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性

注:

  • 自定义Hook以use开头,且use后的字母要大写

自定义Hook——所有的组件在创建和销毁时都进行打印

  • 组件被创建:打印 组件被创建了
  • 组件被销毁:打印 组件被销毁了

image-20230404161751779

image-20230404161758920

自定义Hook——Context的共享

image-20230404161934780

自定义Hook——获取滚动位置

image-20230404162001534

redux hooks的使用

  • 在之前的redux开发中,为了让组件和redux结合起来,我们使用了react-redux中的connect
    • 但是这种方式必须使用高阶函数结合返回的高阶组件
    • 且必须编写:mapStateToProps和 mapDispatchToProps映射的函数
  • useSelector的作用是将state映射到组件中
    • 参数一:将state映射到需要的数据中
    • 参数二:可以进行比较来决定是否组件重新渲染;(后续讲解)
  • useSelector默认会比较我们返回的两个对象是否相等
    • 也就是我们必须返回两个完全相等的对象才可以不引起重新渲染
  • useDispatch非常简单,就是直接获取dispatch函数,之后在组件中直接使用即可
  • 我们还可以通过useStore来获取当前的store对象

redux的常规使用——使用connect函数

整个项目index.js代码:

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './stero';
const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
<Provider store={store}>
<App/>
</Provider>
);

countSlice.js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createSlice } from "@reduxjs/toolkit";

const countSlice = createSlice({
name:"count",
initialState:{
count:5
},
reducers:{
addCountAction(state, action){
state.count += action.payload
}
}
})

export const {addCountAction} = countSlice.actions
const countReducer = countSlice.reducer
export default countReducer

导出的stero的index.js代码:

1
2
3
4
5
6
7
8
9
import { configureStore } from "@reduxjs/toolkit";
import countReducer from "./countSlice";

const store = configureStore({
reducer:{
counter:countReducer
}
})
export default store

stero使用的jsx代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { memo, useLayoutEffect, useState} from 'react'
import { connect } from 'react-redux';
import { addCountAction } from './stero/countSlice';

function App(props) {
const {count, addCount} = props
function buttonCLick(){
addCount(3)
}
return (
<div>
<h2>count:{count}</h2>
<button onClick={buttonCLick}>+3</button>
</div>
)
}

function mapStateToProps(state){
return {
count:state.counter.count
}
}
function mapAction20Props(dispatch){
return {
addCount(count){
dispatch(addCountAction(count))

}
}
}
export default connect(mapStateToProps, mapAction20Props)(memo(App))

注:

  • 这种方式必须使用高阶函数结合返回的高阶组件
  • 并且必须编写:mapStateToProps和 mapDispatchToProps映射的函数

redux的Hook使用

useSelector与useDispatch的使用

App.jsx代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { memo} from 'react'
import { useDispatch, useSelector } from 'react-redux';
import ChildComponent from './component/ChildComponent';
import { addCountAction } from './stero/countSlice';

function App(props) {
console.log("App渲染")
/**
* useSelector的第一个参数为函数,其返回一个对象,会把这个返回对象作为useSelector的值
*/
const {count} = useSelector((state)=>({
count:state.counter.count
}))
/**
* dispatch通过useDispatch直接获取,不再需要像
*/
const dispatch = useDispatch()
function buttonCLick(){
dispatch(addCountAction(4))
}
return (
<div>
<h2>count:{count}</h2>
<ChildComponent/>
<button onClick={buttonCLick}>改变count</button>
</div>
)
}
export default memo(App)

注:

可以看到能够直接使用useSelector对stero中的数据进行映射,直接通过useDispatch获取dispatch

但是上述会出现一些问题:

  • useSelector是监听的是state,也就是state改变后,所有使用useSelector的组件都会刷新

ChildComponent代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, {  memo } from 'react'
import { useSelector } from 'react-redux'

function ChildComponent(props) {
console.log("子组件渲染")
const {message} = useSelector((state)=>({
message:state.counter.message
}))
return (
<div>
<h2>子组件:{message}</h2>
</div>
)
}
export default memo(ChildComponent)

子组件只与state中的message有关,但当count改变时子组件依然会刷新,会引起性能的降低

动画

给useSelector设置shallowEqual就可以避免上述问题,实际上是进行一个浅层比较,如果相同就不更新

1
2
3
4
5
6
7
const {message} = useSelector((state)=>({
message:state.counter.message
}),shallowEqual)

const {count} = useSelector((state)=>({
count:state.counter.count
}),shallowEqual)

动画