单向数据流始祖 Flux
Flux理念的一个更强实现Redux
结合React和Redux
01|Flux
从Flux设计理念到设计缺陷,最后引出更好的状态管理 Redux
-
Flux和React同时面世的,FB认为两者结合能够构建大型的JS应用
- React替换jQuery
- Flux替换Backbonejs和Ember.js等以MVC一族的框架
Flux并不是MVC的设计模式
02|MVC存在的缺陷
-
MVC所存在的缺陷:
- 分而治之历年的 MVC的引入让项目变得非常的复杂
- 不同模块之间的依赖关系让系统变得 脆弱并且不可预测!
- MVC数据流的理念: Controller ===> Model===> View
- 实际上是View和Model可以相互通信!
-
认知偏差:
- 服务器端:从请求开始 Controller===>Model===>View 在后端来讲是一个严格的单向数据流!
- 浏览器端: 界面渲染之后,Model和View还存在开发者为了简便,让View和Model直接通信!
MVC应该对应的让数据更加的可控,Controller应该是中心,当View需要传递消息给Model的时候,同样的,当Model需要更新View的时候,也应该通过Controller引发新的渲染!
start=>start: Action
op1=>operation: Controller
op2=>operation: Model
io1=>inputoutput: View
start->op1->op2->io1->op2
- Action:表示用户的动作
- Controller:控制器
- Model:数据模块
- View:视图部分!
但是从Flux来讲,总的一点就是:更加严格的数据流控制!
start=>start: Action
op1=>operation: Dispatch
op2=>operation: Store
op3=>operation: View
op4=>inputoutput: Action
end=>end: Dispatch
start->op1->op2->op3->op4->op1
对应的Flux(架构/应用)包括如下四个方面:
- Action: 处理动作分发,维持Store之间的依赖关系!
- Dispatch: 负责存储数据和处理数据相关的逻辑
- Store: 驱动Dispatcher的JS对象
- View: 视图部分,负责显示用户界面!
那么直接与MVC比较的话是这样的关系!
- Action:给MVC框架的用户请求
- Dispatcher:Controller
- Store:Model
- View:View
03|比较:MVC和Flux架构
MVC和Flux中功能的增加:
- MVC模式需要加Controller函数
- Flux中的Dispatcher并不需要增加函数,只需要增加对应的Action类型!
04|关于Flux的使用
npm install --save flux
// yarn add flux
- Dispatcher.js
import {Dispatcher} from "dispatcher";
export default Dispatcher;
- ActionType.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
- Action.js
import * as ActionTypes from "./ActionType";
import AppDispatcher from "./Dispatcher";
export const increment = ()=>{
AppDispatcher.dispatcher({
type:ActionTypes.INCREMENT,counterCaption:counterCaption
});
};
export const decrement = (counterCaption)=>{
AppDispatcher.dispatcher({
type:ActionTypes.DECREMENT,counterCaption:counterCaption
});
};
为了让我们更好的理解Store的构建过程,可以看下这段代码:
const counterValues = {
'First':0,'Second':10,'Thrid':30
}
const CounterStore = Object.assign({},EventEmitter.property,{
getCounterValues(){return counterValues;},
emitChange(){this.emit(CHANGE_EVENT);},
addChangeListener(callback){
this.on(CHANGE_EVENT,callback);
},
removeChangeListener(callback){
this.removeListener(CHANGE_EVENT,callback);
}
})
store状态发生变化的时候,通知应用的其他部分作出相应,对应的响应部分就是视图View,但是数据与视图不应该建立硬编码的练习! 因此在代码中引入了EventEmitter(事件发布)对象!
对应事件发布对象中应该有三个方法,对应的:
- on
- emit
- removeListener/off
但是对应的项getCounterValues的函数,拿到的应该是一个不可改变的数据,也就是通过该方法拿到的数据也不应该修改该数据! 不应该修改通过Store拿到的数据! 在对应的 CounterStore中添加对应的代码!
import AppDispatcher from "../AppDispatcher";
CounterStore.dispatcher - AppDispatcher.register((action)=>{
if(action.type === ActionTypes.INCREMENT ){
counterValues[action.counterCaption]++;
CounterStore.emitChange();
}else if(action.type === ActionTypes.DECREMENT){
counterValues[action.counterCaption]--;
CounterStore.emitChange();
}
})
对应的使用register接受的回调函数参数才是Flux才是最核心的部分!
一个action对象会被派发给所有的回调函数,那么对应的调用回调函数的顺序是如何的?
对应的分发过程获取到的值可能是还未更新的得到的,对应的解决方案便是使用 waitFor !
Dispatcher中的waitFor接受一个数组做为参数,对应的数组中的每个元素都是一个Dispatcher.gister函数的返回结果! 也就是所谓的DispatchToken
waitFor表示Dispatcher当前的处理必须要暂停,直到dispatchToken代表的那些已注册的回调函数执行结束才能够继续!
05|View
View指代的是一种UI,至于说UI是什么东西,可以使用任意一种UI库予以实现!
存在于Flux框架中的React组件需要实现以下几个功能
- 创建的时候要读取Store上状态来初始化组件的内部状态!
- Store上面的状态发生改变的时候,组件要立即同步更新内部状态保持一致!
- View如果说要改变对应的Store状态,必须并且只能够派发action!
对应的在初始化的时候,通过引入Flux的Store作为React组件的Store! 与此同时,在组件的不同状态使用生命周期函数对数据进行监听操作!
但是涉及到一个很普遍的问题,在使用数据的时候,通过在构造器 Constructor中对数据进行初始化操作,更改数据的时候使用 this.setState 的时候对数据进行操作! 同样的动作重复了两次 有没有什么更好的办法呢?
与此同时就涉及到了 无状态组件! ====> 函数组件(本身不具有状态的function组件)
06|Flux的优势
如果说只是用React的方式实现的效果一样,对应的工作方式就有很多问题!
如果说组件的状态过多并且多个组件的状态需要关联的话,维护起来就比较麻烦了! 但是 React只提供了对应的props方法让组件之间通信,组件的关系稍微复杂,该方式就出现了问题!
那么该问题对于Flux来讲优势就体现出来了,对应的将状态抽离出来,对应的React只是作为UI这部分! 对应状态管理与维护就轻松了很多了!
与此同时,Flux以一种比较严格的数据流控制方式,确实比较方便的解决了数据一致性的问题!
Flux架构严格的管控了View和Model之间的联系,与MVC相比取而代之的是通过dispatcher对应的action的操作!
Flux的体系/架构下,驱动界面改变的是动作的派发! 落实到事件的过程中 其实就是 始于用户的交互!
07|Flux的不足
-
Store之间依赖关系
- 多个Stroe之间有逻辑依赖的话需要使用到 waitFor
- 对应的使用 dispatchToken 作为store的标识!
-
难以进行服务器端渲染
- 对应的服务端渲染输出的不是DOM树而是对应的字符串! 输出的全是HTML的字符串!
- 在Flux体系中,有一个全局的Dispatcher,每一个Store都是一个唯一的对象,对于浏览器来说是没有问题的,但是对于服务端就会有问题!
- 和浏览器不同的是网页只服务于一个用户,对应的服务器端同时接受多个用户请求,每个Store都是全局唯一的对象,不同的请求状态肯定就乱了!
- 对应的不是说Flux不能够做服务端渲染只是说Flux做服务端渲染很困难!
-
Store混乱了逻辑状态
- 对应的按照面向对象的模式设计Flux的Store是没有问题的,但是如果说要替换Store但是又要保留存储中的状态! 对应的代码出现Bug的时候,通过动态替换的Store来验证Bug是否被修复!
08|Redux的基本原则
- Flux的基本原则是单向数据流,Redux在此基础上做了三个原则的强调
- 唯一数据源
- 保持状态只读
- 数据改变只能通过纯函数完成
- 唯一数据源
应用中只保持一个数据源! 虽然说可以有多个Store,只是Redux框架下面,一个应用拥有多个Store不会带来任何好处,不如一个Store组织代码方便!
- 保持状态只读
对应的数据源不能够直接去修改,需要通过action对象完成! 不是直接修改对象的值,而是通过新的状态对象返回给Redux!
- 数据改变只能够通过 纯函数完成 (Redux = Reducer + Flux)
对应的Reducer其实就是连续操作器,Reduce做的事情就是把数组的所有元素一次做规约,每次元素都执行一遍Reducer,通过Reducer函数完成规约所有元素的功能!
- reducer函数接收两个参数,第一个参数是上一次规约的结果! 这一次当前规约的元素! 函数体是返回两者之和,规约结果是所有元素之和!
reducer(state,action);
对应的Reducer的应用如下:
import ActionTypes from "./ActionType"
function reducer(state,action){
let {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;
}
}
对应的action里面包括对应的类型和具体的参数!
Reducer对应的只负责计算并不负责存储状态!
Redux 的限制虽然说增加了约束,限制了开发者的灵活性
如果你愿意限制做事方式的灵活度,你几乎总会发现可以做得更好!
09|Redux实例
对应的 Redux和React是两个独立的工具,Redux是负责状态的,React对应的是UI这块的!
但是如果说两种结合起来的效果就好很多, 其中一个库将 Redux和React结合起来 没有理由不叫 react-redux
其中和Flux在文件构造上面其实是差不多的,分为actionType和对应的 action部分!
ActionTypes就是对应的给Action分类使用的,Redux对应的返回action对象! 对应的数据的操作交由 Reducer 操作!
10|容器组件和傻瓜组件
- 容器组件:负责和Redux和Store打交道,读取Store的状态!
- 傻瓜组件:负责对应的页面渲染操作! 通过对应的Props进行数据的渲染并不需要State!
- 组件Context
这里就涉及到了对应的状态的传递,一个应用中保证只有一个地方需要引入Store,当然最好的位置就是顶层组件,但是如果说涉及到子组件需要使用对应的数据的话,通过props传统的方式进行传递,显然非常的不合理,因为之前的将状态抽离到React之外就是避免porps跨层传递的这个问题!
因为该问题的出现,React中提供了一个叫做Context的功能,能够比较方便的功能! Context上下文环境,能够让DOM树上面,能够访问到一个共同的对象! 但是要通过配置的前提下才行!
- 上级组件宣称自己支持context,并且提供一个函数返回代表context的对象!
- 上级组件对应的子孙组件,只需要宣称自己需要该context就可以通过 this.context拿到对应的环境对象!
对应的使用context的时候,使用构造器对props和context进行初始化的时候,可以像下面的代码那样进行:
import React,{Component} from "react";
class Provider extends Component{
constructor(props,context){
super(props,context); //or super(...arguments);
}
}
export default Provider;
对应的为了能够使容器组件访问到context必须给容器组件的contextType和对应子组件的childContextType给定一样的值不然的话,就不能够获取到context!
- 对应的该功能要谨慎使用,中间组件可能使用的对象才有必要使用context! 千万不要滥用!
11|React-Redux
其中文中讲到了两种改善React应用的方法分别为:
- 将一个组件拆分为容器组件和傻瓜组件
- 使用React的Context来提供一个所有组件都可以直接访问的Context
当然可以通过上面的方法有两个特性:就是两种方法都有套路,完全可以把套路部分抽取出来复用,这样每个组件的开发只需要关注与不同部分的就行了!
对应的 这里又提到了一个库 react-redux 方便的是我们可以不需要提供Provider而是直接使用库给我们提供的!
对应的react-redux提供的两个功能分别为:
- connect
- 接收两个参数份分别为: mapStateToProps和mapDispatchToprops 除此之外传入的参数返回的函数通过圆括号传入对应的参数继续执行!
- 其中对应的connect做了那两件事情呢?
- 把Store上面的状态转化为内层傻瓜组件的prop
- 把内层傻瓜组件中的用户动作转化为派送给Store的动作!
- 其实一个就是内层傻瓜组件的输入,一个是内层傻瓜组件的输出!
- Provider
- 对应的react-redux的store更加的严谨,对应的Provider中的store要求三个参数分别为:
- subscribe
- dispatch
- getState
对应的React中所提供的componentWillReceiveProps(组件将收到props)函数将在每次重新渲染的时候用到,其主要目的就是判断这一次的接受到的prop与上一次是否一样,不一样则提出警告,其主要目的就是避免多次渲染用到了不同的redux store!