最近在学习 Redux
的相关知识,然后在学习的过程中又发现了一个与它关系十分密切的 Flux
,而且除了这两个之外,还有一个 React-Redux
,为了弄清它们三者之间的差异,所以就打算抽点时间来整理整理 Redux
、Flux
和 React-Redux
这三者的关系与区别,我们就先从一切的起源 Flux
开始看起
什么是 Flux
Flux
是 Facebook
用于构建客户端 Web
应用程序的基本架构,我们可以将 Flux
看做一种应用程序中的数据流的设计模式,而 Redux
正是基于 Flux
的核心思想实现的一套解决方案,Flux
应用中的数据以『单一方向流动』的,它有以下几个特点
- 视图产生动作消息,将动作传递给调度器
- 调度器将动作消息发送给每一个数据中心
- 数据中心再将数据传递给视图
也就是下图当中所示的流程
我们可以将上图简化为以下流程
1 | View(视图层) ==> Action(请求层) ==> Dispatcher(传输层) ==> Store(处理层) ==> 最后再次回到 View |
比如用户在视图上(view
)点击了一个按钮,即发送了一个 action
,然后 action
发送到 dispatcher
中(调度器),dispatcher
来分配这个 action
(比如要指派给谁去做任务)给 store
(在一个 Flux
结构中,store
可以有多个,注意和 React-Redux
区分),在 store
中的作用就是存储并修改数据,然后传递给 view
进行渲染(渲染到虚拟 DOM
当中),单一方向数据流还具有以下特点
- 集中化管理数据,常规应用可能会在视图层的任何地方或回调进行数据状态的修改与存储,而在
Flux
架构中,所有数据都只放在store
中进行储存与管理 - 可预测性,在双向绑定或响应式编程中,当一个对象改变时,可能会导致另一个对象发生改变,这样会触发多次级联更新,对于
Flux
架构来讲,一次action
触发,只能引起一次数据流循环,这使得数据更加可预测 - 方便追踪变化,所有引起数据变化的原因都可由
action
进行描述,而action
只是一个纯对象,因此十分易于序列化或查看
Flux 的工作流
当我们在使用 MVC
或者 MVVM
架构设计模式的时候,有一个缺点,就是当项目越来越大,逻辑越来越复杂的时候,数据间的流动就会显得十分混乱,而 Flux
就是致力于解决数据有序传输问题的架构设计模式,其中最大的哲学就是『数据是单向流动的』
下面我们就来了解一下 Flux
当中的工作流,可以如下图所示
在 Flux
中我们可以看到会有以下几个角色的出现
dispatcher
,调度器,接收到action
并将它们发送给store
action
,动作消息,包含动作类型与动作描述store
,数据中心,持有应用程序的数据,并会响应action
消息view
,应用视图,可展示store
数据,并实时响应store
的更新
下面我们就来分别看看它们各自的作用
Dispatcher
dispatcher
接收action
,并且要把这些action
分派给已经注册到dispatcher
的store
上- 所有的
store
都将接收所有的action
- 在每个
App
中,应该确保只有一个dispatcher
的实例
Store
store
是在App
中持有数据的东西,stores
将要在App
的dispatcher
身上注册,以确保它们可以接收actions
- 存在
store
中的数据只能够因为响应action
才能有所改变 - 在
store
中不能够有公共的setter
函数,仅能够有getter
函数 stores
决定了它们愿意响应哪些actions
- 无论什么时候,
store
中的数据改变了,就会触发一个change
事件 - 在一个
App
中可能有很多store
Action
action
定义了我们App
中内部的API
- 它们捕获所有可能改变
App
的任何途径、方法 - 它们是简单的
JSON
对象,并且要有type
属性,和其他一些数据属性,也就是下面这样
1 | { |
action
应该具有一个语义化的命名,比如上面我们定义的这个删除操作的action
,我们一眼就可以看出需要执行的是删除操作,它对应的id
为123
- 所有的
store
都将接收同一个action
,并且通过这同一个action
,store
会知道它们要清除和更新哪些数据
Views
- 从
store
中来的数据将被展示在view
上 view
层可以使用任何框架- 当一个视图想要使用从某一个
store
中来的数据,它必须订阅subscribe
(订阅)一下该store
的change
事件 - 当
store
发射(emit
)了change
事件,此时view
就能够得到新的数据并且重新渲染 - 如果一个组件要使用
store
,但是没有订阅这个store
,就会出现问题(bug
) action
最常见的产生原因实在App
的某一个部分因用户的交互行为,而被此view
dispatch
(派发) 出来了
什么是 Redux
在知道了什么是 Flux
以后,我们再来看看 Redux
的相关内容,简单来说,Redux
就是 Flux
思想在 React
当中的实现,所谓的 Redux
可以简单的理解为一个可以预测状态的 JavaScript
的 App
容器,而 App
中的全部 state
都被存储在一个单独的 store
中,形式是 object-tree
(JSON
),唯一更改 state
的途径就是 emit
一个 action
,这个 action
描述了发生了什么
为了指定这些 actions
如何改变 state tree
,必须书写简单的、纯净的 reducers
,所谓的纯净的 reducers
就是类似下面这样伪代码,它不继承任何东西,并且无论何时返回的值都是固定的
1 | function reducers(state, action) { |
上面就是一个 reducer
,是一个纯函数,接收 state
和 action
两个参数,返回新的 state
表达式,如果有使用过 Flux
,在这里我们可以发现有一个重要的区别
即在
Redux
当中没有dispatcher
的概念(store
自己负责dispatch
某个action
到自己身上),也不允许有多个store
,所以一般来说,Redux
比较适合用于有强的全局数据概念的Web
应用(比如商城,购物车等)
Redux
中只有一个唯一的 store
,使用唯一的 reducing function
,随着项目增长的时候也不要去增加 store
,而是应该切割当前的 store
为一个个小的 store
,即 store
应该只有一个,类似于 React
当中只允许使用一个根节点,但是根节点是由众多的节点组成,我们下面将会分别进行讨论
为什么要用 Redux
这个需要视当前的使用场景来决定的,当然除了 Redux
还有 Flux
、Reflux
、Mobx
等状态管理库可供选择,下面就是一个实际场景,比如在控制台上记录用户的每个动作
1 | // 后端,比如使用 Express 中实现一个简单的 Logger |
然后现在又需要在上述需求的基础上,记录用户的操作时间
1 | // 后端,只需要稍微修改一下原来的中间件即可 |
又比如说,在正式上线的时候,把控制台中有关 Logger
的输出全部去掉,亦或是自动收集 bug
,很明显的可以看出前后端对于这类需求的处理竟然大相径庭,原因在于,后端具有统一的入口与统一的状态管理(数据库),因此可以引入中间件机制来统一实现某些功能,而前端也可以使用 MVC
的开发思维,将应用中所有的动作与状态都统一管理,让一切有据可循
Store
我们首先要区分 store
和 state
之间的区别,state
是应用的状态,一般本质上是一个普通对象,例如我们有一个 Web App
,包含计数器和待办事项两大功能,那么我们可以为该应用设计出对应的存储数据结构(应用初始状态)
1 | /』应用初始 state『/ |
而 store
则是应用状态 state
的管理者,包含下列四个函数
getState()
,获取整个state
dispatch(action)
,触发state
改变的【唯一途径】subscribe(listener)
,可以理解成是DOM
中的addEventListener
replaceReducer(nextReducer)
,一般在Webpack Code-Splitting
按需加载的时候用(使用较少)
二者的关系是 state = store.getState()
Redux
,规定,一个应用只应有一个单一的store
,其管理着唯一的应用状态state
Redux
,还规定,不能直接修改应用的状态state
,也就是说,下面的行为是不允许的
1 | var state = store.getState() |
若要改变
state
,必须dispatch
一个action
,这是修改应用状态的不二法门
- 针对
action
,暂时只需要记住,action
就是一个包含type
属性的普通对象,例如{ type: 'INCREMENT' }
- 而
store
,我们需要调用Redux
提供的的createstore()
方法,如下
1 | import { createStore } from 'redux' |
- 针对
reducer
,暂时只需要记住,reducer
是一个 函数,负责更新并返回一个新的state
即可 - 而第二个参数
initialState
主要用于前后端同构的数据同步(详情请关注React
服务端渲染)(可暂时不用管)
Action
action
(动作)实质上是包含 type
属性的普通对象,这个 type
是我们实现用户行为追踪的关键,例如增加一个待办事项的 action
可能是像下面一样
1 | { |
action
的形式是多种多样的,唯一的约束仅仅就是包含一个 type
属性
1 | // 下面这些 action 都是合法的,但就是不够规范 |
具体规范可见 flux-standard-action
Action Creator
Action Creator
是 action
的创造者,本质上就是一个函数,返回值是一个 action
(对象)(可以是同步的,也可以是异步的),例如下面就是一个新增一个待办事项的 Action Creator
1 | var id = 1 |
简单来说,Action Creator
就是用于绑定到用户的操作(比如点击按钮等),其返回值 action
用于之后的 dispatch(action)
Reducer
需要注意的是,reducer
必须是同步的『纯函数』,简单来说分为以下三步
- 用户每次
dispatch(action)
后,都会触发reducer
的执行 reducer
的实质是一个函数,根据action.type
来更新state
并返回nextState
- 最后会用
reducer
的返回值nextState
完全替换掉原来的state
几个需要注意的地方
- 所谓的更新并不是指
reducer
可以直接对state
进行修改 Redux
规定,须先复制一份state
,在副本nextState
上进行修改操作- 例如,可以使用
lodash
的cloneDeep
,也可以使用Object.assign/map/filter ...
等返回副本的函数
例如下面这个示例
1 | var initState = { |
简单的理解就是,reducer
返回什么,state
就被替换成什么
Redux 的整体流程
store
由Redux
的createstore(reducer)
生成state
通过store.getState()
获取,本质上一般是一个存储着整个应用状态的对象action
本质上是一个包含type
属性的普通对象,由action Creator
(函数) 产生- 改变
state
必须dispatch
一个action
reducer
本质上是根据action.type
来更新state
并返回nextState
的函数reducer
必须返回值,否则nextState
即为undefined
- 实际上,
state
就是所有reducer
返回值的汇总
大致流程如下所示
1 | Action Creator |
Redux 官方示例剖析
下面我们就通过一个例子来深入的了解一下 Redux
的工作流程,示例参考的是官方提供的 counter-vanilla
(见 redux/examples/counter-vanilla/index.html
)
1 | // reducer |
action
所对应的字段一般都是约定成俗的使用大写字母来进行表示,它描述了一个 action
如何使当前 state
改变为下一个 state
,state
的形式取决于你,它可以是一个基本类型值,可以是一个数组,也可以是一个对象等等,唯一需要注意的就是,永远不要去更改当前的 state
,而是应该返回一个新的 state
对象
1 | var store = redux.createstore(counter) |
首先创建一个 Redux
的 store
,用它来持有 App
的 store
,store
的 API
及其简单,就三个,subscribe
,dispatch
和 getState
subscribe
,让store
去注册一个视图dispatch
,分发一个命令getState
,返回一个状态
1 | store.subscribe(render) |
使用 store
的 subscribe()
方法,将 store
订阅了视图,render
是一个函数,其实简单来说就是,每次当 state
变化的时候就会执行该函数,通常情况下是与 React
来配合使用,调整上面的示例,添加一个每次点击增加 2
的按钮
1 | // reducer |
调整示例,添加一个输入框,然后点击的时候加上输入框内的值
1 | function counter(state = 0, action) { |
综合以上示例,点击按钮的时候,使 store
去 disptch
一个命令,这时需要注意了,数据存储在 store
中,然后 store
给自己 dispatch
了一条命令,然后自己再去识别给自己发送的命令(case
),然后改变存储在自己 store
中的 state
(return
)
之所以这样设计,就是因为在 reducer
中可以看见整个程序的 state
会发生怎样的变化,虽然不知道什么时候会变化,但是知道其可以做出什么样的变化,知道其不能够做出什么样的变化,这就是 Redux
的哲学,让 state
可以被预期,这也就是下面的 reducer
存在的意义
1 | // reducer 清单 |
综上
- 我们不是直接去修改
state
,而是指定了一个简单的JSON
对象(类似指令,type
)去描述我们想要什么事情发生,这个JSON
称之为action
- 然后声明一个特定的
reducer
的函数去指定每一个action
要如何改变整个App
的store
注意这个『整个』,看下面的示例,我们先将 state
默认值设置为一个对象(不再是简单的数字)
1 | // reducer |
直接使用类似上面的 return state.m + 1
是没有效果的,这时需要返回的是整个 state
的值(建议使用 ES6
中的 ...
运算符)即
1 | // reducer |