理解Javascript的状态容器Redux
Redux要解决什么问题?
随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。
管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。
Redux的设计借鉴Flux、 Elm、Immutable,它的出现就是为了解决state里的数据问题。Redux和Flux的主要区别是Redux里面是单一的数据源设计,而Flux(或者Reflux)里面有多个数据源。
Redux是如何工作的?
我们知道,在React中,数据在组件中是单向流动的。数据从一个方向父组件流向子组件(通过props),由于这个特征,两个非父子关系的组件(或者称作兄弟组件)之间的通信并不是那么清楚。
React并不建议直接采用组件到组件的通信方式,尽管它有一些特性可以支持这么做(比如先将子组件的值传递给父组件,然后再由父组件在分发给指定的子组件)。这被很多人认为是糟糕的实践方式,因为这样的方式容易出错而且会让代码向“拉面”一样不容易理解。
当然React也没有直接建议如何去处理这种情形,以下是React的文档中关于这部分的描述:
对于非父子关系的组件,你可以自己建立一个全局的事件系统,Flux的模式也是一种可行的方式。
Redux的出现就让这个问题的解决变得更加方便了。Redux提供一种存储整个应用状态到一个地方的解决方案(可以理解为统一状态层),称为“store”,组件将状态的变化转发通知(dispatch)给store,而不是直接通知其它的组件。组件内部依赖的state的变化情况可以通过订阅store来实现。
使用Redux,所有的组件都从store里面获取它们依赖的state,同时也需要将state的变化告知store。组件不需要关注在这个store里面其它组件的state的变化情况,Redux让数据流变得更加简单。这种思想最初来自Flux,它是一种和React相同的单向数据流的设计模式。

Redux的核心设计理念
Redux有三大原则
- 单一数据源,整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
- State 是只读的,惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
- 使用纯函数来执行修改,为了描述 action 如何改变 state tree ,你需要编写 reducers。
单一数据源的设计让React的组件之间的通信更加方便,同时也便于状态的统一管理。
根据Redux的文档,状态变化的唯一方式是触发一个action(一个可以描述发生了什么的对象),这意味着我们不能直接的去修改状态,取而代之的是我们可以通过转发action去告诉store我们有改变状态的意图。store对象提供了非常少的API,仅仅只有4个方法:
store.dispatch(action)
store.subscribe(listener)
store.getState()
replaceReducer(nextReducer)
通过这几个API不难发现,store并没有直接提供setState()方法。
另外,由于它大量使用 pure function 和 plain object 等概念(reducer 和 action creator 是 pure function,state 和 action 是 plain object)这对于项目的稳定性会是非常好的保证。
理解Action、Reducer
一个action的例子:
var action = {
type: 'ADD_USER',
user: {name: 'Dan'}
};
// 假设store对象已经通过Redux.createStore()创建
store.dispatch(action);
这段代码中,通过dispatch() 方法将action传递给了store。Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
前面描述过,Redux不允许直接去改变state,必须通过转发action来告诉store有这个意图要去改变这个状态。reducer正是扮演处理转发过来的action的意图的函数并且可以改变状态的角色。一个reducer接受当前的state作为参数,通过返回新的state去改变原有的state:
var someReducer = function(state, action) {
...
return state;
}
由于reducer是纯函数,需要注意:
- 不允许在reducer函数内部进行网络调用或者数据库查询操作
- 不能改变它的参数
这样做的好处是:每次调用同样的一个函数,传入相同的值可以得到相同的结果。对系统的其它部分也不会产生副作用。
第一个Redux store
使用Redux.createStore()来创建一个store,并且将所有的reducers作为参数传递给它,此处以一个reducer为例子:
var userReducer = function(state=[], action) {
if (action.type === 'ADD_USER') {
var newState = state.concat([action.user]);
return newState;
}
return state;
}
// 创建一个store,并且将reducer作为参数传递给它
var store = Redux.createStore(userReducer);
// 将action传递给store,告诉store我们有改变状态的意向
store.dispatch({
type: 'ADD_USER',
user: {name: 'cpselvis'}
});
上述代码运行后发生的事情:
- Store被创建
- reducer确定初始的state的值是空数组
- action被转发给store
- reducer将newUser添加到state中,并且将它返回,更新store
通过这段代码可以发现:reducer函数实际上被执行了2次,一次是store创建的时候,一次是action被转发之后。另外需要注意:Redux希望reducer函数总是返回一个新的状态。这时的结果:
store.getState(); // => [{name: 'cpselvis'}]
通过这个例子,可以总结出Redux的工作流程如图:

到这里,component -> action -> store -> reducer -> state 的单向数据流的问题就讲完了。概括的说就是:React组件里面获取了数据之后(比如ajax请求),然后创建一个action通知store我有这个想改变state的意图,然后reducers(一个action可能对应多个reducer,可以理解为action为订阅的主题,可能有多个订阅者)来处理这个意图并且返回新的state,接下来store会收集到所有的reducer的state,最后更新state。
理解Javascript的状态容器Redux的更多相关文章
- 深入理解JavaScript系列(43):设计模式之状态模式
介绍 状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类. 正文 举个例子,就比如我们平时在下载东西,通常就会有好几个状态,比如准备状态(ReadySta ...
- 深入理解JavaScript 事件
本文总结自<JavaScript高级程序设计>以及自己平时的经验,针对较新浏览器以及 DOM3 级事件标准(2016年8月),对少部分内容作了更正,增加了各种例子及解析. 如无特殊说明,本 ...
- 深入理解JavaScript系列(37):设计模式之享元模式
介绍 享元模式(Flyweight),运行共享技术有效地支持大量细粒度的对象,避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类). 享元模式可以避免大量非常相似类的开销,在程序设 ...
- 深入理解JavaScript系列(18):面向对象编程之ECMAScript实现(推荐)
介绍 本章是关于ECMAScript面向对象实现的第2篇,第1篇我们讨论的是概论和CEMAScript的比较,如果你还没有读第1篇,在进行本章之前,我强烈建议你先读一下第1篇,因为本篇实在太长了(35 ...
- 深入理解JavaScript运行机制
深入理解JavaScript运行机制 前言 本文是写作在给团队新人培训之际,所以其实本文的受众是对JavaScript的运行机制不了解或了解起来有困难的小伙伴.也就是说,其实真正的原理和本文阐述的并不 ...
- 深入理解javascript系列(4):立即调用的函数表达式
本文来自汤姆大叔 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下“自执行”这个叫法,本文对这个功能的叫法 ...
- 深入理解JavaScript系列
转自http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html 深入理解JavaScript系列(1):编写高质量JavaScript代码 ...
- 深入理解javascript作用域系列第一篇——内部原理
× 目录 [1]编译 [2]执行 [3]查询[4]嵌套[5]异常[6]原理 前面的话 javascript拥有一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量,这套规则被称为作用域.作用域 ...
- JavaScript可否多线程? 深入理解JavaScript定时机制
JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不少人都深有同感, 例如 setTimeout( ...
随机推荐
- 分区表在安装系统(MBR)丢失或损坏
操作系统能识别出硬盘中的各个不同的分区,是靠硬盘分区表(MBR)来识别的. 硬盘分区表中记录了各个分区的位置和大小以及类型等信息,假设这个分区表破坏了,那么这块硬盘里面的分区就会丢失.系统是无法在浏览 ...
- 使用SQL Server 2005数据库管理工具 - 初学者系列 - 学习者系列文章
本文讲述使用SQL Server 2005 Express数据库管理工具的使用. 1.打开数据库管理工具 2.选择下面的SQL Server 身份验证,因为在安装数据库的时候设置了sa的密码. 3.选 ...
- 使用Java编写的B*算法
package rpg.stage.path; import java.util.ArrayList; import java.util.HashSet; import java.util.Itera ...
- 介绍开源的项目管理系统-Redmine
介绍开源的项目管理系统-Redmine 分类: Redmine2009-06-01 10:12 1047人阅读 评论(0) 收藏 举报 项目管理subversionphpmyadminrailsaut ...
- 10 Super Useful Tools for Web Designers
36个扁平化设计的iphone和ipad应用界面设计案例 http://designwoop.com/2014/04/36-examples-of-flat-iphone-and-ipad-appli ...
- iOS基础 - Modal展示控制器
一.利用Modal形式展示控制器 1.如何展示 // vc就是要展示的新控制器 [self presentViewController:vc animated:YES completion:^{ NS ...
- 修改Ubuntu Server的分辨率
修改Ubuntu Server的分辨率,其实就是找到关键的文件,然后修改参数,重启一下,就可以看见效果了. 1.打开文件并修改参数 文件位置:/etc/default/grub 修改位置:GRUB_C ...
- 流媒体:V4L2视频获取
从深圳回来已经20多天了,除了完善毕业设计程序和论文,其他时间都去玩游戏了.真的是最后的一段时间能够无忧无虑在校园里挥霍自己的青春了.今天完成的答辩,比想象的要简单,一直以来想把我现在的这个流媒体的东 ...
- vijos1009:扩展欧几里得算法
1009:数论 扩展欧几里得算法 其实自己对扩展欧几里得算法一直很不熟悉...应该是因为之前不太理解的缘故吧这次再次思考,回看了某位大神的推导以及某位大神的模板应该算是有所领悟了 首先根据题意:L1= ...
- 企业架构研究总结(41)——企业架构与建模之ArchiMate的由来和详述(上)
终于完成了关于企业架构框架理论的总结,谢谢各位看官的支持,能挺过之前过于理论化的叙述而坚持到现在着实不易,笔者也自愧没有实践经验可以分享,希望日后有兴趣的看官能够不吝赐教.在本系列后面的也是最后一个大 ...