一. 认识ReduxToolkit
Redux Toolkit
是官方推荐的编写Redux
逻辑的方法- 在前面我们学习
Redux
的时候应该已经发现,redux
的编写逻辑过于的繁琐和麻烦 - 并且代码通常分拆在多个文件中(虽然也可以放到一个文件管理,但是代码量过多,不利于管理)
Redux Toolkit
包旨在成为编写Redux
逻辑的标准方式,从而解决上面提到的问题- 在很多地方为了称呼方便,也将之称为“
RTK
”
- 在前面我们学习
安装
Redux Toolkit
:Redux ToolKit
本质上其实是对编写redux
做的一层封装,如果想在react
中使用,还是需要安装react-redux
包才行redux toolkit
内部有依赖redux
,所以不需要再安装redux
了shellnpm i @reduxjs/toolkit react-redux
Redux Toolkit
的核心API
主要是如下几个:configureStore
:- 包装
createStore
以提供简化的配置选项和良好的默认值。它可以自动组合你的slice reducer
,添加你提供的任何Redux
中间件,默认包含redux-thunk
,并启用Redux DevTools Extension
- 包装
createSlice
:- 接受
reducer
函数的对象、切片名称和初始状态值,并自动生成切片reducer
,并带有相应的actions
- 接受
createAsyncThunk
:- 接受一个动作类型字符串和一个返回承诺的函数,并生成一个
pending/fulfilled/rejected
基于该承诺分派动作类型的thunk
- 接受一个动作类型字符串和一个返回承诺的函数,并生成一个
二. ReduxToolkit
重构
1. 重构代码 – 创建counter的reducer
我们先对
counter
的reducer
进行重构: 通过createSlice
创建一个slice
createSlice
主要包含如下几个参数:name
:用户标记slice
的名词- 在之后的
redux-devtool
中会显示对应的名词
- 在之后的
initialState
:初始化值- 第一次初始化时的值
reducers
:相当于之前的reducer
函数- 对象类型,并且可以添加很多的函数
- 内部定义的函数类似于
redux
原来reducer
中的一个case
语句,会被放在createSlice
的action
中,action
中具有type
和payload
两个属性 - 函数的参数:
- 参数一:
state
- 参数二:调用这个
action
时,传递的action
参数
- 参数一:
createSlice
返回值是一个对象,包含所有的actions
,reducer
注意:导出
slice
的时候,需要导出reducer
属性
2. 重构代码 – 创建home的reducer
3. store的创建
configureStore
用于创建store
对象,常见参数如下:reducer
:将slice
中的reducer
可以组成一个对象传入此处middleware
:可以使用参数,传入其他的中间件devTools
:是否配置devTools
工具,默认为true
4. RTK结合React-Redux使用

三. ReduxToolkit
异步
1.Redux Toolkit
的异步操作
在之前的开发中,我们通过
redux-thunk
中间件让dispatch
中可以进行异步操作Redux Toolkit
默认已经给我们集成了Thunk
相关的功能:createAsyncThunk
函数第一个参数,是为了方便在
redux
中查看所对应的name
第二个参数是一个函数,接收两个参数,该函数的第一个参数是
payload
,第二个参数是store
为什么需要
return
呢?因为只有return
结果出去,之后才能在对应的fulfilled
中第二个参数的payload
中拿到return
的数据当**
createAsyncThunk
创建出来的action
**被dispatch
时,会存在三种状态:pending
:action
被发出,但是还没有最终的结果fulfilled
:获取到最终的结果(有返回值的结果),会传入到第二个参数action
中action
rejected
:执行过程中有错误或者抛出了异常
我们可以在
createSlice
的extraReducer
属性中监听这些结果:
2. extraReducer的另外写法
写法一:
extraReducer
还可以传入一个函数,函数接受一个builder
参数我们可以向
builder
中添加case
来监听异步操作的结果:
写法二:
- 注意:直接在
createAsyncThunk
的第二个参数中dispatch
对应的action
,需要从xxxSlice
的actions
属性中导出对应的action
- 注意:直接在
3.Redux Toolkit
的数据不可变性(了解)
在
React
开发中,我们总是会强调数据的不可变性:- 无论是类组件中的
state
,还是redux
中管理的state
- 事实上在整个
js
编码过程中,数据的不可变性都是非常重要的
- 无论是类组件中的
所以在前面我们经常会进行浅拷贝来完成某些操作,但是浅拷贝事实上也是存在问题的:
- 比如过大的对象,进行浅拷贝也会造成性能的浪费
- 比如浅拷贝后的对象,在深层改变时,依然会对之前的对象产生影响
事实上**
Redux Toolkit
底层使用了immerjs
的一个库来保证数据的不可变性**coderwhy
公众号的一片文章中也有专门讲解immutable-js
库的底层原理和使用方法:https://mp.weixin.qq.com/s/hfeCDCcodBCGS5GpedxCGg
为了节约内存,又出现了一个新的算法:
Persistent Data Structure
(持久化数据结构或一致性数据结构)用一种数据结构来保存数据
当数据被修改时,会返回一个新对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费
所以在
createSlice
函数中传入的reducer
中,对state
可以直接修改,因为底层不会修改原数据,而是生成一个新的数据,在比较state
的时候,也就会更新state
四. connect
高阶组件
1.自定义connect
函数
connect.js
jsximport { PureComponent } from 'react' import store from '../store' // 参数一:函数 // 参数二:函数 // 返回值:函数 => 高阶组件 export default function connect(mapStateToProps, mapDispatchToProps) { return function(WrapperComponent) { class NewComponent extends PureComponent { constructor(props) { super() this.state = mapStateToProps(store.getState()) } componentDidMount() { this.unsubscribe = store.subscribe(() => { // this.forceUpdate() // 强制重新渲染组件 this.setState(mapStateToProps(store.getState())) }) } componentWillUnmount() { this.unsubscribe() } render() { const stateObj = mapStateToProps(store.getState()) const dispatchObj = mapDispatchToProps(store.dispatch) return <WrapperComponent { ...this.props } { ...stateObj } { ...dispatchObj } /> } } return NewComponent } }
类组件中使用
jsximport React, { PureComponent } from 'react' import connect from '../hoc/connect' import { addNumber } '../store/features/counter' export class About extends PureComponent { render() { const { counter } = this.props return ( <div> <h2>About Counter: { counter }</h2> </div> ) } } const mapStateToProps = state => ({ counter: state.counter.counter }) const mapDispatchToProps = dispatch => ({ addNumber(num) { dispatch(addNumber(num)) } }) export default connect(mapStateToProps, mapDispatchToProps)(About)
2.context
处理store
- 但是上面的
connect
函数有一个很大的缺陷:依赖导入的store
,如果我们将其封装成一个独立的库,需要依赖用于创建的store
,我们应该如何去获取呢? - 难道让用户来修改我们的源码吗?不太现实
- 正确的做法是我们提供一个
Provider
,Provider
来自于我们创建的Context
,让用户将store
传入到value
中即可

五. 中间件的实现原理
1.打印日志需求
- 前面我们已经提过,中间件的目的是在
redux
中插入一些自己的操作:- 比如我们现在有一个需求,在
dispatch
之前,打印一下本次的action
对象,dispatch
完成之后可以打印一下最新的store state
- 也就是我们需要将对应的代码插入到
redux
的某部分,让之后所有的dispatch
都可以包含这样的操作
- 比如我们现在有一个需求,在
- 如果没有中间件,我们是否可以实现类似的代码呢? 可以在派发的前后进行相关的打印
- 但是这种方式缺陷非常明显:
- 首先,每一次的
dispatch
操作,我们都需要在前面加上这样的逻辑代码 - 其次,存在大量重复的代码,会非常麻烦和臃肿
- 首先,每一次的
- 是否有一种更优雅的方式来处理这样的相同逻辑呢?
- 我们可以将代码封装到一个独立的函数中
- 但是这样的代码有一个非常大的缺陷:
- 调用者(使用者)在使用我的
dispatch
时,必须使用我另外封装的一个函数dispatchAndLog
- 显然,对于调用者来说,很难记住这样的
API
,更加习惯的方式是直接调用dispatch
- 调用者(使用者)在使用我的
2.修改dispatch
事实上,我们可以利用一个
hack
一点的技术:Monkey Patching
,利用它可以修改原有的程序逻辑我们对代码进行如下的修改:
- 这样就意味着我们已经直接修改了
dispatch
的调用过程 - 在调用
dispatch
的过程中,真正调用的函数其实是dispatchAndLog
- 这样就意味着我们已经直接修改了
当然,我们可以将它封装到一个模块中,只要调用这个模块中的函数,就可以对
store
进行这样的处理:
3.thunk
需求
redux-thunk
的作用:- 我们知道
redux
中利用一个中间件redux-thunk
可以让我们的dispatch
不再只是处理对象,并且可以处理函数 - 那么
redux-thunk
中的基本实现过程是怎么样的呢?事实上非常的简单
- 我们知道
我们来看下面的代码:
我们对
dispatch
进行转换,这个dispatch
会判断传入的
4.合并中间件
单个调用某个函数来合并中间件并不是特别的方便,我们可以封装一个函数来实现所有的中间件合并:
应用到
store
中我们来理解一下上面操作之后,代码的流程:
当然,真实的中间件实现起来会更加的灵活,这里我们仅仅做一个抛砖引玉,有兴趣可以参考
redux
合并中间件的源码流程
六. React
状态管理选择
1. React中的state如何管理
- 我们学习了Redux用来管理我们的应用状态,并且非常好用(当然,你学会前提下,没有学会,好好回顾一下)
- 目前我们已经主要学习了三种状态管理方式:
- 方式一:组件中自己的state管理
- 方式二:Context数据的共享状态
- 方式三:Redux管理应用状态
- 在开发中如何选择呢?
- 首先,这个没有一个标准的答案
- 某些用户,选择将所有的状态放到redux中进行管理,因为这样方便追踪和共享
- 有些用户,选择将某些组件自己的状态放到组件内部进行管理
- 有些用户,将类似于主题、用户信息等数据放到Context中进行共享和管理
- 做一个开发者,到底选择怎样的状态管理方式,是你的工作之一,可以一个最好的平衡方式(Find a balance that works for you, and go with it.)
2. React中的state如何管理
Redux
的作者有给出自己的建议:- 目前项目中我采用的
state
管理方案:UI
相关的组件内部可以维护的状态,在组件内部自己来维护- 大部分需要共享的状态,都交给
redux
来管理和维护 - 从服务器请求的数据(包括请求的操作),交给
redux
来维护
- 当然,根据项目的实际情况进行适当的调整