# 理解 Redux
# 快速导航
# 前言
在 React 中,数据流是单向的,并且是不可逆的,这其实,也很好理解,之所以这么设计,是因为组件复用的特点
父(外部)组件向子(内部)组件传递数据是通过自定义属性 props 值的方式进行实现的,并且在子组件内部通过this.props
进行获取,它并不能直接被修改,如果想要修改,那么得通过 React 内置的一个setState
的方法进行触发
而子组件想要传递数据给父组件,是通过调用父组件的方法进行通信
一个组件可能存在着很多状态,组件之间有时需要进行通信,对于多个组件状态维护,如果依旧用原来的方式,那么就比较复杂了的
那么 Redux 正好解决了这一问题.个人觉得,Redux 学起来很抽象,的确是块硬骨头,但是高山始终是要越过的
下面就一起来学习下 Redux 的
您将在本文中学习到
- Redux 是什么
- Redux 的使用场景以及与不使用 Redux 的灵魂对比
- Redux 的工作流程
- Redux 的设计基本原则
本篇虽不涉及代码层面上的,但是对后续编码 Redux 非常重要,磨刀不误砍柴工
# Redux 是什么?
官方解释: JavaScript
应用程序的可预测的状态容器(一个管理应用程序状态的框架)
通俗一点:管理组件公共数据状态的容器(仓库/区域)
解决的问题: 当应用组件拥有多个状态,并且组件之间需要共享数据状态时,从原始的组件传递数据的方式中解脱出来,集中管理组件的状态
你可以把 Redux 理解为一个仓库,房产中介.拥有很多共享的房源的一个管理者,后面会有具体的例子
# Redux 的使用场景
从上面提到的 Redux
解决问题可以看出,Redux 只是用来管理和维护组件的状态的
React
开发的模式就是组件化开发,将一个大的应用拆分成若干个小的应用,然后拼接成一个大的应用,而编写一个大小应用就是在编写各个大小组件
而组件的显示形态又取决于它的状态,这不区分于无论是外部的 props 还是内部的 state,而组件之间有时需要共享传递数据,Redux
仅仅就是用来管理这些组件的状态的
在一些开发者眼里,项目里要是没有用到Redux
,就觉得很 low,要么把 Redux 捧得高高在上,要么说都已经快 0202 年了,都用React hook
了,鄙视得不行,个人觉得完全没有必要.
React 与 Redux 本身就是解决两个不同方向的问题,某种程度上讲,React 可以视为 MVC 架构中的视图层 V,而 Redux 则是 model 数据层 M,而 C 层往往是连接视图层和 model 的连接器,往往处理前端数据请求,路由跳转等业务逻辑
即使不用 Redux,照样能做小应用,只是略复杂繁琐一些而已,下面会介绍他们之间的对比
那么对于技术选型,什么时候用 Redux 什么时候不用?
以下是选用Redux
的场景:
项目非常庞大,公共组件与业务组件非常多,用户的使用方式比较复杂
不同身份的用户角色权限管理(例如很多后台管理系统,普通用户,超级管理员,VIP 用户)读,写权限管理等
多个用户之间可以协作实时操作(很多那种在线敏捷协作办公文档工具,多个用户可以实时编辑操作同一份文档等的,例如石墨文档,语雀,confluence.钉钉等的)
需要与服务器大量的交互,或者使用了 webscoket 的,聊天,直播等应用的
视图层 view 需要从多个来源获取数据
只要你发现 React 解决不了的问题,遇到多交互,多数据源的,那么就可以考虑使用 Redux 的
反之,则以下则是没有必要使用 Redux
UI 层非常简单,只是用于渲染,无复杂的数据交互,依赖外部的 props 就可以渲染组件
用户的使用方式比较简单,页面之间比较独立,没有互相协作
与服务器之间没有大量交互
当你发现使用React
实在解决不了的问题,在各个组件之间传递数据非常复杂,很痛苦时,那么就可以考虑使用Redux
了的,只要你 hold 住,没有所谓的高大上技术,只有适合自己业务的技术
盲目引入 Redux 只会增加项目的复杂度,引入新的技术应该是循序渐进的
# 不使用 Redux 与使用 Redux 的灵魂对比
下面这张组件树状态图的对比就很好的解释了使用 Redux 与不使用 Redux 的区别
一个React应用(例如:pc网站,手机app应用,后台管理系统等用React技术栈构建的应用)其实就是一颗由组件构成的树如上图所示,在这颗树的根结点,最顶层的组件就是该应用的本身,由于组件都是以树结构组织起来的,当每个组件被渲染时,它都会递归地渲染下级组件
根节点就是最顶层的组件,该应用本身
一个大的应用是由各个组件拼装而成的
在小型项目中,Redux 并不是必需的,但是使用 Redux 却是一劳永逸的,管理组件的状态方便得多,对于大型应用来说,单纯使用原始的数据传递方式
那么组件之间的传值会变得非常复杂,如果要做一个大型的应用,那么就需要在 React 的基础上配置一个数据层的框架进行结合的使用
假设红色圆圈代表的是一个应用的子组件,如果想要把该红色圆圈组件的状态数据传递给父级或者非父级组件,它是通过调用父组件的方法来实现,这样一层一层往上传,如果组件树很庞大的话,那么就会变得非常繁琐
在小型项目中,Redux 并不是必需的,但是使用 Redux 却是一劳永逸的,管理组件的状态方便得多,对于大型应用来说,单纯使用原始的数据传递方式
那么组件之间的传值会变得非常复杂,如果要做一个大型的应用,那么就需要在 React 的基础上配置一个数据层的框架进行结合的使用
如果改为右边的 Redux 处理方式,将红色圆圈组件的状态数据放到一个 Store 仓库当中集中进行管理,哪个组件需要的话,直接派发给哪个组件就可以了的.
在 Redux 中,要求把组件的数据放到公共的存储仓库(区域)当中,让组件尽可能的减少状态数据存储,换而言之,所有组件自身内部状态数据都不放在 state 里面了,把它放到 Store 这样的一个存储仓库当中去
其实本质上来说,是放到 reducer 里面去管理,Store 从 Reducer 中拿到返回的数据 state,最后供外部组件的取用
当红色圆圈组件想要改变数据传递给其他组件时,只需要去改变 Store 里面的存储红色圆圈组件的数据就可以了
一旦 Store 公共存储的状态数据发生改变了的,由于其他组件是公用 Store 的数据,那么其他组件就会感知到 Store 的数据发生了改变,从而自身组件也会跟着改变
只要 Store 公共存储区域的数据发生改变,凡是共用了 Store 里面的数据的组件都会重新的取数据
这样一来,红色圆圈组件的数据就非常容易的传递给其他组件了,无论是它的父级组件还是兄弟,非兄弟组件的
Redux 就是把组件的数据放到一个公共的区域(仓库)中进行存储,当改变 Store 存储区域里面的数据时,其他组件如果用到了公共区域的数据,那么就会感知到数据的变化,它会自动的更新取 Store 中最新的数据
这样话,无论你的应用组件嵌套得有多么复杂,多么深,走的流程都是一样的,组件之间并不会干扰,低耦合的效果
当组件一修改,把修改的数据放到 Store 当中,而其他组件又从 Store 当中再来取,这样的话,组件与组件之间并不是直接进行通信的,是通过这么一个 store 中间角色来实现数据的传递共享的.
这样的话,组件的数据传递就简单多了的,也避免了组件与组件之间频繁通信,容易产生混乱的问题
Redux 其实是 Flux 数据框架的一个替代演进,同样强调的是单向的数据源,保持状态只具备读的能力,而数据改变只能通过纯函数完成基本,这和原先中 React 的 UI=render(data)完全吻合.
React 与 Redux 是两个独立的框架,前者是用于组件视图层的渲染,而后者是管理组件的数据
# Redux 的工作流程
现在已经知道了使用 Redux 与不使用 Redux 的区别,那么现在是时候来了解一下 Redux 的工作流程了,下面这个流程图对于理解 Redux 很重要 先附上一张 Redux 工作流的流程图:以后会在代码中逐步的体现
上面的 Redux 工作流图中,以中间为准:包括了Store
,ReactComponents
,Actions Creators
,以及Reducers
其中 Store 代表的就是负责组件存储所有公共状态的数据,全局只有一个 Store.(这里你可以把它理解为类似生活当中中介公司管理房源的仓库(数据库)的区域经理)
实质上:store
就是把 Reducer
关联到一起的一个对象,它提供 dispatch(action)方法更新 state,以及 getState 方法获取 state
React Components:指的是页面上的任意一个组件(你可以理解为小区公寓楼里的每个房间,而你就是住在里面的租房用户)
Actions Creators:具体要干什么事情,触发的动作,可以看做一个交互动作,改变应用状态或 view 的更新,都需要通过触发 action 来实现,Action 的执行结果就是调用 Dispatch
来处理相应的事情,实现页面视图 view 的更新,唯一的办法就是调用 dispatch 派发 action
它是一个 javascript 对象
,是用来描述事件的行为的,对象里记录了相关的信息,例如:todolist 的添加,删除 list 的这个具体操作,就是一个 action
(当你想要提出换房的时候,跟中介公司管理房源的经理说,你要换个带有沙发,电视,配备厨房的两室一厅的房子,因为增加人口了,现有的房子住不下了的,你要做的什么事情,提出的条件信息就是数据),这个动作可以理解为 actions creators
在你提出换房的时候,房产中介公司经理虽然手握很多房源,但是他也没有办法记得所有的房子相关信息,它需要去数据库(仓库)里去查,你常常看到中介小哥带你看房的时候
手上拿一个单子,Excel 表格跟你介绍房源的时候,你可以把这个单子,Excel 表格理解为一个实时记录本,只要有房子出租去了,这个表格就会实时更新(新旧信息的核实对比),返回一张新的房源信息表单给房产中介的经理
Reducer:可以把上面的用于实时更新记录房源信息的记录本称为 Reducer,它只用作于根据旧的房源与提出新的需求(动作),总是会返回一张新的记录本给房产中介经理
实质上:Reducer
是根据 action 发出的 type(动作类型)来做什么事(返回最新的 state 给 store 等逻辑操作)
现在归纳一下整个流程:
我(租客/组件React Component
)想要换一个 xxx 信息的房子(Actions creators
,具体要做的什么事情),房产中介经理收到了请求,他得根据你提供的一些需求信息去找相应的房源信息
但是房源太多,需要借助一个实时的记录本去查看符合条件的房源信息,当查到符合条件的信息后,这个记录本(Reducer
)把最新的信息会返回给房产经理(Store
),最终把信息返回给用户React Components
,实现房子替换的更新
虽然文字啰嗦了点:但是 Redux 就是这么一回事,我要换大房子,房产中介经理听到后,它去记录本里面去查,查到之后,返回到房产中介经理,然后最终在返回给我,实现房子的替换
那么转换为代码理解:
页面上的一个组件,想要获取更新 Store 中的数据,跟 Store 说,我点击这个按钮,要更新这个组件的数据,要干什么事情,做的这个具体动作就是Actions Creators
,这时会派发(dispatch
) 该动作(action
)给Store,Store
会去Reducer
里面去查一下,Reducer
会返回一个新的结果给Store
,Store
拿到最新的数据结果后,返回给页面上的组件,实现页面组件的更新
大家可以先仔细体会上面这段文字的含义,在后续的实例代码中,在回过头来对比着代码与文字进行理解的,后续还会在拿出来的
# Redux 的设计基本原则
在 Redux 中有以下几个设计基本原则
- 单向数据流
- 唯一数据源
- 保持状态只读
- 数据的改变只能通过纯函数 reducer 来完成
单向数据流: 这个其实与 props 不能直接被修改一样,在父组件向子组件传递数据时是通过属性的方式进行传递的,而子组件内部通过this.props
进行接收,但是外部传递过来的props
属性不能直接被修改,若想要修改,需要借助React
内置的setState
方法进行触发
唯一数据源: 它指的是组件的应用状态数据应该只存在唯一的 Store 上,这一点是不同于 Flux 的,在 Flux 中允许有多个 store。而在 Redux 中整个组件的应用只保持一个 Store,所有组件的数据源就是这个 Store 上的状态,可以将它 Store 理解为一个全局的变量对象
保持状态 state 可读: 不能直接的去修改状态,要修改 Store 的状态,必须要通过派发(dispatch
)一个action
对象去完成
然后组件渲染的对应的界面要更改的话,实际更改的就是组件的状态,如果状态都是只能读不能修改的话,那么界面就不会更新变化了
想要更改用户界面的渲染,就要改变组件的应用状态,但时改变组件状态的方法不是直接去修改状态上的值,而是创建一个新的状态对象返回给 Redux,由 Redux 完成新的状态的组装
组件数据的改变只能通过纯函数完成
所谓的纯函数,就是指 Reducer,而 Redux 某种程度上讲,它是 Reducer+Flux 的组合,其中这 Redux 的 Red 代表就是 Reducer,而 ux 就是 Flux,但是又不同于 Flux,它更像是 Flux 的一个实现,演进。它是为了描述 Action 如何改变组件的状态的
这也是为什么 Redux 这个名称比较抽象的原因,其中 Reducer 类似一个数组中的迭代器函数 reduce
点击即可查看
var arr = [1,2,3,4,5,6]
var sum = arr.reduce(function reducer(prevValue, currentValue,index,array){
console.log(`上一次调用回调返回的值(或者是提供的初始值): ${prevValue},数组中当前被处理的元素: ${currentValue}, 当前元素在数组中的索引: ${index}, 调用的数组: ${array}`);
return prevValue+currentValue;
},0)
console.log(sum); // 21
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 0,数组中当前被处理的元素: 1, 当前元素在数组中的索引: 0, 调用的数组: 1,2,3,4,5,6
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 1,数组中当前被处理的元素: 2, 当前元素在数组中的索引: 1, 调用的数组: 1,2,3,4,5,6
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 3,数组中当前被处理的元素: 3, 当前元素在数组中的索引: 2, 调用的数组: 1,2,3,4,5,6
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 6,数组中当前被处理的元素: 4, 当前元素在数组中的索引: 3, 调用的数组: 1,2,3,4,5,6
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 10,数组中当前被处理的元素: 5, 当前元素在数组中的索引: 4, 调用的数组: 1,2,3,4,5,6
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 15,数组中当前被处理的元素: 6, 当前元素在数组中的索引: 5, 调用的数组: 1,2,3,4,5,6
VM1742:6 21
2
3
4
5
6
7
8
9
10
11
12
13
14
上面的代码中是做一个简单的累加,reducer
函数接收四个参数,第一个参数是上一次调用返回的结果,第二个参数是当前被处理的元素的值,第三个是当前元素在数组中的索引,第四个是调用的原数组
这个 reduce 的方法接收一个函数作为累加器,reduce 为数组中的每一个元素依次执行回调函数
而在 Redux 中,每个 reducer 纯函数如下所示
reducer(state, action)
其中reducer
函数的第一个参数state
是指当前的状态值,而第二个参数action
是接收到的 action 对象
而reducer 函数要做的事情就是根据 state 和 action 的值产生一个新的对象返回给 Store,它是定义整个组件应用状态如何更改,根据 Action 动作行为去更新 Store 中的状态 ::: warn 注意 reducer 必须是纯函数,换句话说,reducer 函数的返回结果必须完全由参数 state 和 action 决定,而且不产生任何的副作用,也不能修改参数 state 和 action 对象 :::
如下一个典型的 reducer 示例,reducer 只是一个函数名称,你是可以任意取的,如下一个计数的 counter 纯函数
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
2
3
4
5
6
7
8
9
10
从上面的例子看得出,reducer
函数不光接受action
为参数,还接受state
参数,也就是说,Redux
中的reduce
函数只负责计算组件的状态,却不负责存储组件的状态
在Reducer
函数中往往包含action.type
为判断条件的if-else
或者switch
语句
根据 action,总是返回一个新的状态,这个新的状态的结果返回给 store,store
就会将原来上一次的 state 进行替换更新,最终达到改变 state 这么一个过程
# 总结
本节主要介绍了 Redux,它与 React 是两个独立的产品,两个框架做的事情的方向不一样,React 是用作于视图层的渲染,也相当于 MVC 中的 V 层,而 Redux 它是用于管理组件公共数据的 Model 层,更近一步讲,它是 Reducer 与 Flux 的一种结合,改进.
对比了使用 Redux 与不使用 Redux 的区别,以及 Redux 的工作流,最后 Redux 的设计基本原则,其中前两个,个人觉得对于理解 Redux 是非常重要的
当然现在也可以使用高阶组件,React hooks
的写法,可以不用Redux
了的,也有类似于dva
这样的框架,基于 Redux 以及中间件(Redux-saga)的数据流方案
但是 Redux 依然是主流,只要你能够应付项目中开发需求,哪个用得爽就用哪个的,Redux 虽然确实是绕了一些,有时候在各个文件之间进行来回切换,对于模块化的拆分,如果不是很清楚 Redux 的使用流程,无论是后续维护还是迭代升级,都挺痛苦的