• Flux框架的概念与应用


    笔记,参考程墨老师的《深入浅出React和Redux》

    Flux概念

    image.png

    • Dispatcher,处理动作分发,维持Store之间的依赖关系
    • Store,负责存储数据和处理数据相关逻辑
    • Action,提供给Dispatcher,传递数据给Store
    • View,视图部分,负责显示用户界面

    首先会产生一个事件,一般是由用户对界面执行的一个操作,Action得到这个操作后,将其交给Dispatcher,再由Dispatcher来进行分发给Store, Store收到通知后对相关数据进行维护,再发出一个更改通知,告诉视图层需要更新View了,然后重新从Store中检索数据,调用setState方法对View进行更新。

    Flux应用

    示例

    源码分支02flux




    安装

    npm install --save flux
    

    Dispatcher

    src/AppDispatcher.js

    //1.从flux库导入Dispatcher
    //Dispatcher用来派发action
    import {Dispatcher} from 'flux'
    export default new Dispatcher();
    

    Action

    src/AcitonTypes.js

    //1.定义actions类型
    export const INCREMENT='increment'
    export const DECREMENT='decrement'
    

    src/Actions.js

    //2.定义action构造函数
    //	这是能够产生并派发action对象的函数(increment和decrement)
    //	只要这两个函数被调用,就会创造对应的action对象 并通过AppDispatcher.dispatch派发出去
    import * as AcitonTypes from './ActionTypes';
    import AppDispatcher from './AppDispatcher'
    
    export const increment = (counterCaption)=>{
        AppDispatcher.dispatch({
            type:AcitonTypes.INCREMENT,
            counterCaption:counterCaption
        })
    }
    
    export const decrement = (counterCaption)=>{
        AppDispatcher.dispatch({
            type:AcitonTypes.DECREMENT,
            counterCaption:counterCaption
        })
    }
    

    Store

    store对象
    - 存储应用状态
    - 接受Dispatcher派发的动作
    - 根据动作来决定是否要更新应用状态
    
    [例子]有3个Counter组件,1个统计3个Counter计数值之和的组件
    - 为Counter组件服务的CounterStore
    - 为总数服务的SummaryStore
    

    src/stores/CounterStore.js

    import AppDispatcher from '../AppDispatcher'
    import * as AcitonTypes from '../ActionTypes';
    import {EventEmitter} from 'events';
    const CHANGE_EVENT='changed'
    const counterValues = {
        'First': 0,
        'Second': 10,
        'Third': 30
    };
    const CounterStore=Object.assign({},EventEmitter.prototype,{
        //1.用于让应用中其他模块读取当前的计数值
        getCounterVlues:function(){
            return conterValues
        },
        
        //2.定义监听
        emitChange:function(){
            this.emit(CHANGE_EVENT)
        },
        addChangeListener:function(callback){
            this.on(CHANGE_EVENT,callback)
        },
        removeChangeListener:function(callback){
            this.removeListener(CHANGE_EVENT,callback)
        }
    })
    

    EventEmitter.prototype可以让对象变成EventEmitter对象。EventEmitter的实例对象支持下列相关函数:

    • emit函数,广播特定事件,第一个参数是字符串类型的事件名称;
    • on函数,增加一个挂在这个EventEmitter对象特定事件上的处理函数,第一个参数是字符串类型的事件名称,第二个参数是处理函数;
    • removeListener函数,和on函数做的事情相反,删除挂在这个EventEmitter对象特定事件上的处理函数。
      三者完成:更新的广播、添加监听函数和删除监听函数
    //3. 把CounterStore注册到全局唯一的Dispatcher上去
    //4. Dispatcher有一个register函数,接受回调函数作为参数
    //5. 返回值是一个token,这个token可以用于Store之间的同步
    //	 返回值token在稍后的SummaryStore中会用到,现在保存在CounterStore.dispatchToken中
    CounterStore.dispatchToken=AppDispatcher.register((action)=>{
        if(action.type===AcitonTypes.INCREMENT){
            counterValues[action.counterCaption]++
            //6.更新监听
            CounterStore.emitChange()
        }else if(action.type===AcitonTypes.DECREMENT){
            counterValues[action.counterCaption]--
            CounterStore.emitChange()
        }
    })
    export default CounterStore
    

    当通过register函数把一个回调函数注册到Dispatcher之后,所有派发给Dispatcher的action对象,都会传递到这个回调函数中来。比如通过Dispatcher派发一个动作:

    AppDispatcher.dispatch({
           type:AcitonTypes.INCREMENT,
           counterCaption:'First'
    })
    //1. 现在,当在CounterStore注册(register)的回调函数就会被调用
    //	 register的参数就是这个派发的action对象
    //2. 回调函数会根据action对象来决定改如何更新自己的状态
    //   比如该动作含义:名为First的计数器要做加一动作。根据不同的type,会有不同的操作
    //3. 所以注册的回调函数很自然有一个模式,就是函数体是if-else条件语句或者switch条件语句
    //	 条件语句的跳转条件,都是针对参数action对象的type字段
    //4. 无论是加一或者减一,最后都要调用CounterStore.emitChange函数。
    //	 此时,如果有调用者通过CounterStore.addChangeListner关注了CounterStore的状态变化
    //	 这个emitChange函数调用就会引发监听函数的执行
    

    src/stores/SummaryStore.js

    import CounterStore from './CounterStore'
    import AppDispatcher from '../AppDispatcher'
    import * as AcitonTypes from '../ActionTypes';
    import {EventEmitter} from 'events';
    const CHANGE_EVENT='changed'
    
    function computeSummary(counterValues){
        let summary=0
        for(const key in counterValues){
            if(counterValues.hasOwnProperty(key)){
                summary+=counterValues[key]
            }
        }
        return summary
    }
    
    const SummaryStore=Object.assign({},EventEmitter.prototype,{
        getSummary:function(){
            return computeSummary(CounterStore.getCounterValues())
        },
    
        emitChange: function() {
        	this.emit(CHANGE_EVENT);
        },
        addChangeListener: function(callback) {
        	this.on(CHANGE_EVENT, callback);
        },
        removeChangeListener: function(callback) {
        	this.removeListener(CHANGE_EVENT, callback);
        }
    })
    

    SummaryStore并没有存储自己的状态。当getSummary被调用时,它是直接从CounterStore里获取状态计算。SummaryStore提供了getSummary让其他模块可以获得所有计数器当前值的总和。

    SummaryStore.dispatchToken=AppDispatcher.register((action)=>{
        if((action.type===AcitonTypes.INCREMENT)||(action.type===AcitonTypes.DECREMENT)){
            AppDispatcher.waitFor([CounterStore.dispatchToken]);
            SummaryStore.emitChange()
        }
    })
    export default SummaryStore
    
    //1. SummaryStore同样通过AppDispatcher.register函数注册回调函数,用于接受派发的action对象
    //	 在回调函数中,只关注INCREMENT和DECREMENT类型的action对象。并通过emitChange通知监听者
    //2. waitFor函数解决了调用顺序问题
    //   这时,在CounterStore中注册回调函数时保存下来的dispatchToken终于派上了用场。
    //   调用waitFor的时候,把控制权交给Dispatcher
    //   让Dispatcher检查dispatchToken代表的回调函数有没有被执行,如果已经执行,那就直接接续;
    //   如果还没有执行,那就调用dispatchToken代表的回调函数之后waitFor才返回
    

    Dispatcher的register函数,只提供了注册一个回调函数的功能,但不能让调用者在register时选择只监听某些action。即当一个动作被派发的时候,Dispatcher就是简单地把所有注册的回调函数全都调用一遍

    View

    Flux框架下,View并不是说必须要使用React, View本身是一个独立的部分,可以用任何一种UI库来实现。
    存在于Flux框架中的React组件需要实现以下几个功能:
    1.	创建时要读取Store上状态来初始化组件内部状态;
    2.	当Store上状态发生变化时,组件要立刻同步更新内部状态保持一致;
    3.	View如果要改变Store状态,必须而且只能派发action。
    

    src/views/ControlPanel.js

    class ControlPanel extends React.Component{
        render(){
            return(
            	<div>
                	<Counter caption="First" />
                    <Counter caption="Second" />
                    <Counter caption="Third" />
                    <hr/>
                    <Summary/>
                </div>
            )
        }
    }
    

    src/views/Counter.js

    import * as Actions from '../Actions'
    import CounterStore from '../stores/CounterStore'
    
    class Counter extends React.Component{
        constructor(props) {
            super(props);
            this.onChange = this.onChange.bind(this);
            this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
            this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
            //1.CounterStore.getCounterValues函数获得了所有计数器的当前值
    		//然后把this.state初始化为对应caption字段的值
            this.state = {
              count: CounterStore.getCounterValues()[props.caption]
            }    
        }
    
    
        //2.现在,Counter组件中的state是Flux Store上状态的一个同步镜像
        //为了保持两者一致,当CounterStore上状态变化时,Counter组件也要对应变化
        componentDidMount(){
            CounterStore.addChangeListener(this.onChange)
        }
        componentWillUnmount(){
            CounterStore.removeChangeListener(this.onChange)
        }    
        onChange(){
            const newCount =CounterStore.getCounterValues()[this.props.caption]
            this.setState({count: newCount});
        }
        
        //3.只有当状态不一致时才更新状态
        shouldComponentUpdate(nextProps, nextState) {
            return (nextProps.caption !== this.props.caption) ||
                   (nextState.count !== this.state.count);
        }
        
    	//4.让React组件触发事件时派发action,只需把action对象
        onClickIncrementButton(){
            Actions.increment(this.props.caption)
        }
        onClickDecrementButton(){
            Actions.decrement(this.props.caption)
        }
        
        render(){
            const {caption} =this.props
            return(
            	<div>
                	<input type="button" onClick={this.onClickIncrementButton} value="+"/>
                    <input type="button" onClick={this.onClickDecrementButton} value="-"/>
                    <span>{caption} count:{this.state.count}</span>
                </div>
            )
        }
    }
    

    可见,在Counter组件中有两处用到CounterStore的getCounterValues函数,
    第一处是在构造函数中初始化this.state的时候
    第二处是在响应CounterStore状态变化的onChange函数中
    同样一个Store的状态,为了转换为React组件的状态,有两次重复的调用

    src/views/Summary.js

    import SummaryStore from '../stores/SummaryStore'
    class Counter extends React.Component{
        constructor(props){
            super(props)
            this.onUpdate = this.onUpdate.bind(this);
            this.state={
                sum:SummaryStore.getSummary()
            }
        }
        componentDidMount(){
            SummaryStore.addChangeListener(this.onUpdate)
        }
        componentWillUnmount(){
            SummaryStore.removeChangeListener(this.onUpdate)
        }    
        onUpdate() {
            this.setState({
              sum: SummaryStore.getSummary()
            })
        }
        render(){
            return(
            	<div>
                	total:{this.state.sum}
                </div>
            )
        }
    }
    

    总结

    • 引入Dispatcher对象
    • 定义action类型
    • 定义action构造函数
    • view中触发事件以派发action
    • 编写store状态
      • 定义默认状态值
      • 定义变量以让其它地方获取当前状态
      • 定义监听
      • 注册一个回调,接受派发过来的action对象为参数。接着通过传过来的对象更新store状态;再更新监听(示例中是emitChange),然后调用者通过addChangeListner监听某store的变化,如果有变化,就调用传进来的回调(示例中是onChange),将store中新的状态渲染到视图中

    Flux的架构下,应用的状态被放在了Store中,React组件只是扮演View的作用,被动根据Store的状态来渲染。在上面的例子中,React组件依然有自己的状态,但是已经完全沦为Store组件的一个映射,而不是主动变化的数据。

    用户的操作引发的是一个“动作”的派发,这个派发的动作会发送给所有的Store对象,引起Store对象的状态改变,而不是直接引发组件的状态改变。

    Flux带来了哪些好处呢?最重要的就是“单向数据流”的管理方式。

    在Flux中,如果要改变界面,必须改变Store中的状态,如果要改变Store中的状态,必须派发一个action对象

    Flux的不足

    Store之间依赖关系

    如果两个Store之间有逻辑依赖关系,就必须用上Dispatcher的waitFor函数。SummaryStore对action类型的处理,依赖于CounterStore已经处理过了。

    SummaryStore靠register函数的返回值dispatchToken标识CounterStore,而dispatchToken的产生,当然是CounterStore控制的,即:

    • CounterStore必须要把注册回调函数时产生的dispatchToken公之于众
    • SummaryStore必须要在代码里建立对CounterStore的dispatchToken的依赖

    难以进行服务器端渲染

    在Flux的体系中,有一个全局的Dispatcher,然后每一个Store都是一个全局唯一的对象,这对于浏览器端应用完全没有问题,但是如果放在服务器端,就会有大问题。

    和一个浏览器网页只服务于一个用户不同,在服务器端要同时接受很多用户的请求,如果每个Store都是全局唯一的对象,那不同请求的状态肯定就乱套了。

    Store混杂了逻辑和状态

    Store封装了数据和处理数据的逻辑。但是,当我们需要动态替换一个Store的逻辑时,只能把这个Store整体替换掉,那也就无法保持Store中存储的状态。

    还有一些应用,在生产环境下就要根据用户属性来动态加载不同的模块,而且动态加载模块还希望不要网页重新加载,这时候也希望能够在不修改应用状态的前提下重新加载应用逻辑,这就是热加载(Hot Load)

  • 相关阅读:
    jQuery学习易忘细节
    mysql关键字与自己设置的字段冲突
    jquery导航栏html页面跳转导航字体变色
    css解决谷歌,360浏览器默认最小字体为12px问题
    ThinkPHP中关于JS文件如何添加类似__PUBLIC__图片路径
    (谷歌浏览器等)解决css中点击input输入框时出现外边框方法【outline:medium;】
    为何在font-family属性中设置多个值
    jquery实现简单的Tab切换菜单
    Thinkphp下嵌套UEditor富文本WEB编辑器
    thinkphp框架下404页面设置
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/13763807.html
Copyright © 2020-2023  润新知