第三章 从Flux到Redux——《深入浅出React和Redux》读书笔记
第3章 从Flux到Redux
单向数据流框架的始祖Flux;
Flux理念的一个更强实现Redux;
结合React和Redux。
3.1 Flux
React是用来替换jQuery的,那么Flux就是以替换Backbone.js、Ember.js等MVC一族框架为目的。
在MVC(Model-View-Controller)的世界里,React相当于V(也就是View)的部分,只涉及页面的渲染一旦涉及应用的数据管理部分,还是交给Model和Controller,不过,Flux并不是一个MVC框架,事实上,Flux认为MVC框架存在很大问题,它推翻了MVC框架,并用一个新的思维来管理数据流转。
3.1.1 MVC框架的缺陷
MVC框架是业界广泛接受的一种前端应用框架类型,这种框架把应用分为三个部分:
Model(模型)负责管理数据,大部分业务逻辑也应该放在Model中;
View(视图)负责渲染用户界面,应该避免在View中涉及业务逻辑;
Controller(控制器)负责接受用户输入,根据用户输入调用对应的Model部分逻辑,把产生的数据结果交给View部分,让View渲染出必要的输出。
MVC框架的几个组成部分和请求的关系可以用图3-1表示:
图3-1 MVC框架
这样的逻辑划分,实质上与把以一个应用划分为多个组件一样,就是“分而治之”。毫无疑问,相比把业务逻辑和界面渲染逻辑混在一起,MVC框架要先进得多。
MVC框架提出的数据流很理想,用户请求先到达Con-troller,由Controller调用Model获得数据,然后把数据交给View,但是,在实际框架实现中,总是允许View和Model可以直接通信,从而出现图3-2的情况。
在MVC中让View和Model直接对话就是灾难。
图3-2 MVC的缺点
Flux的一个特点:更严格的数据流控制。
Facebook已经无心在MVC框架上纠缠,他们用Flux框架来代替原有的MVC框架,他们提出的Flux框架大致结构是图3-3的模样。
图3-3 Flux的单向数据流
一个Flux应用包含四个部分,我们先粗略了解一下:
Dispatcher,处理动作分发,维持Store之间的依赖关系;
Store,负责存储数据和处理数据相关逻辑;
Action,驱动Dispatcher的JavaScript对象;
View,视图部分,负责显示用户界面。
如果非要把Flux和MVC做一个结构对比,那么,Flux的Dispatcher相当于MVC的Controller,Flux的Store相当于MVC的Model,Flux的View当然就对应MVC的View了,至于多出来的这个Action,可以理解为对应给MVC框架的用户请求。
在MVC框架中,系统能够提供什么样的服务,通过Con-troller暴露函数来实现。每增加一个功能,Controller往往就要增加一个函数;在Flux的世界里,新增加功能并不需要Dis-patcher增加新的函数,实际上,Dispatcher自始至终只需要暴露一个函数Dispatch,当需要增加新的功能时,要做的是增加一种新的Action类型,Dispatcher的对外接口并不用改变。
当需要扩充应用所能处理的“请求”时,MVC方法就需要增加新的Controller,而对于Flux则只是增加新的Action。
3.1.2 Flux应用
为了使用Flux,首先通过命令行在项目目录下安装Flux。npm install --save flux
1. Dispatcher
首先,我们要创造一个Dispatcher,几乎所有应用都只需要拥有一个Dispatcher,对于我们这个简单的应用更不例外。在src/AppDispatcher.js中,我们创造这个唯一的Dispatcher对象,代码如下:
import {Dispatcher} from 'flux';
export default new Dispatcher();非常简单,我们引入flux库中的Dispatcher类,然后创造一个新的对象作为这个文件的默认输出就足够了。在其他代码中,将会引用这个全局唯一的Dispatcher对象。
Dispatcher存在的作用,就是用来派发action,接下来我们就来定义应用中涉及的action。
2. action
action顾名思义代表一个“动作”,不过这个动作只是一个普通的JavaScript对象,代表一个动作的纯数据,类似于DOMAPI中的事件(event)。甚至,和事件相比,action其实还是更加纯粹的数据对象,因为事件往往还包含一些方法,比如点击事件就有preventDefault方法,但是action对象不自带方法,就是纯粹的数据。
作为管理,action对象必须有一个名为type的字段,代表这个action对象的类型,为了记录日志和debug方便,这个type应该是字符串类型。
3. Store
一个Store也是一个对象,这个对象存储应用状态,同时还要接受Dispatcher派发的动作,根据动作来决定是否要更新应用状态。
最重要的一个步骤,要把Store注册到全局唯一的Dispatcher上去。Dispatcher有一个函数叫做register,接受一个回调函数作为参数。register接受的这个回调函数参数,这是Flux流程中最核心的部分,当通过register函数把一个回调函数注册到Dispatcher之后,所有派发给Dispatcher的action对象,都会传递到这个回调函数中来。
既然一个action对象会被派发给所有回调函数,这就产生了一个问题,到底是按照什么顺序调用各个回调函数呢?
即使Flux按照register调用的顺序去调用各个回调函数,我们也完全无法把握各个Store哪个先装载从而调用register函数。所以,可以认为Dispatcher调用回调函数的顺序完全是无法预期的,不要假设它会按照我们期望的顺序逐个调用。
Dispatcher的waitFor可以接受一个数组作为参数,数组中每个元素都是一个Dispatc-herregister函数的返回结果,也就所谓的dispatchToken。这个waitFor函数告诉Dispatcher,当前的处理必须要暂停,直到dispatchToken代表的那些已注册回调函数执行结束才能继续。
4. view
3.1.3 Flux的优势
“单向数据流”的管理方式。
简单说来,在Flux的体系下,驱动界面改变始于一个动作的派发,别无他法。
3.1.4 Flux的不足
Store之间依赖关系
难以进行服务器端渲染
难以进行服务器端渲染
3.2 Redux
我们把Flux看作一个框架理念的话,Redux是Flux的一种实现
3.2.1 Redux的基本原则
Flux的基本原则是“单向数据流”,Redux在此基础上强调三个基本原则:
唯一数据源(Single Source of Truth);
保持状态只读(State is read-only);
数据改变只能通过纯函数完成(Changes are made withpure functions)。
这里所说的纯函数就是Reducer,Redux这个名字的前三个字母Red代表的就是Reducer。按照创作者Dan Abramov的说法,Redux名字的含义是Reducer+Flux。
在Redux中,每个reducer的函数签名如下所示:
reducer(state, action)
第一个参数state是当前的状态,第二个参数action是接收到的action对象,而reducer函数要做的事情,就是根据state和action的值产生一个新的对象返回,注意reducer必须是纯函数,也就是说函数的返回结果必须完全由参数state和action决定,而且不产生任何副作用,也不能修改参数state和action对象。
Flux更新状态的函数只有一个参数action,因为状态是由Store直接管理的,所以处理函数中会看到代码直接更新state;在Redux中,一个实现同样功能的reducer代码如下:
function reducer(state, action) = > {
const {counterCaption} = action;
switch (action.type) {
case ActionTypes.INCREMENT:
return {...state, [counterCaption]: state[counterCaption] + 1
};
case ActionTypes.DECREMENT:
return {...state, [counterCaption]: state[counterCaption] - 1
};
default:
return state
}
}可以看到reducer函数不光接受action为参数,还接受state为参数。也就是说,Redux的reducer只负责计算状态,却并不负责存储状态。
从Redux的基本原则来看,Redux并没有赋予我们强大的功能,反而是给开发者增加了很多限制。
在计算机编程的世界里,完成任何一件任务,可能都有一百种以上的方法,但是无节制的灵活度反而让软件难以维护,增加限制是提高软件质量的法门。
3.2.2 Redux实例
3.2.3 容器组件和傻瓜组件
在Redux框架下,一个React组件基本上就是要完成以下两个功能:
和Redux Store打交道,读取Store的状态,用于初始化组件的状态,同时还要监听Store的状态改变;当Store状态发生变化时,需要更新组件状态,从而驱动组件重新渲染;当需要更新Store状态时,就要派发action对象;
根据当前props和state,渲染出用户界面。
两个组件是父子组件的关系。业界对于这样的拆分有多种叫法,承担第一个任务的组件,也就是负责和Re-dux Store打交道的组件,处于外层,所以被称为容器组件(Container Component);对于承担第二个任务的组件,也就是只专心负责渲染界面的组件,处于内层,叫做展示组件(Presen-tational Component)。
外层的容器组件又叫聪明组件(Smart Com-ponent),内层的展示组件又叫傻瓜组件(Dumb Component)
3.2.4 组件Context
3.2.5 React-Redux
react-redux的两个最主要功能:
connect:连接容器组件和傻瓜组件;
Provider:提供包含store的context。
1. connect
这个connect函数具体做了什么工作呢?作为容器组件,要做的工作无外乎两件事:
把Store上的状态转化为内层傻瓜组件的prop;
把内层傻瓜组件中的用户动作转化为派送给Store的动作。
这两个工作的套路也很明显,把Store上的状态转化为内层组件的props,其实就是一个映射关系,去掉框架,最后就是一个mapStateToProps函数该做的事情。
mapStateToProps可以包含第二个参数,代表ownProps,也就是直接传递给外层容器组件的props。
2. Provider
Provider即封装好的store,store不光是一个object,而且是必须包含三个函数的object,这三个函数分别是。
subscribe
dispatch
getState
拥有上述三个函数的对象,才能称之为一个Redux的store。每个Redux应用只能有一个Redux Store。
3.3 本章小结
通过react-redux完成了React和Redux的融合。