Flux分析与实践

929 阅读8分钟

Flux分析与实践

缘起:

在做了接近一年的React小程序开发之后,关于程序的状态管理框架,有了一些思考上的变化。项目一开始技术选型是用的Mobx,原因是当时参与选型的同学认为Redux大量的模板代码和学习曲线不适合,这在当时的背景下这并没有什么问题(项目成员都是Native客户端出身,对于前端的技术栈并没有深入了解,加上当时项目的排期非常紧张),但是随着后来团队的调整,开始了漫长的一个人维护整个项目代码的艰难历程,因为项目本身的复杂度,开始对Mobx的感觉发生了一些变化,从一开始的放弃Redux选择Mobx是没有任何理由的正确决定,到后面发觉,结合业务场景来看,Mobx可能并不是一个好的解决方案。当然个人并没有在实际项目中用过Redux,不能直接说二者谁好谁差,因此,我希望在个人的视角上,重新注意并学习和理解Redux,看看Redux是否能够解决目前Mobx存在的一些问题,既是查漏补缺,也算是复盘整个大半年的维护经历。

对于Redux的学习研究,主要以《Redux实战》这本书为例。

因为非专业前端,很多东西仍然要从最简单的开始,比如提到Redux,就不得不提到Flux。

正文:

前端无论是Web,还是Android,架构理念发展了这么多年,个人认为核心点之一还是在于,如何将View层和Model层的关系进行清晰明了,由此诞生了MVC,MVP,MVVM等开发框架,框架会要求我们应该在哪一部分写什么代码,当然,这里传统Android的MVC和Web的MVC的实践上其实是有出入的,不过理念都是一样的。

image.png

关于MVC的理念图,并不止一种,如上所示,这些都是MVC或者MVC的变种,但是规范而言,我们希望不要在View层中去直接操作Model层,Model层的变化通过Controller层去触发,然后Model层变更之后再通知到View层更新视图。

不过,规范如果不强制一点,往往后来人就容易不遵守规范,快速的业务迭代和排期压力使得很多或者不少时候,大家都是怎么快速怎么来,我能在View层中直接改Model层,为啥还需要去通过Controller干这事儿,因此,屎山便来了。

而关于Flux的理念图:

image.png

  • View 视图层
  • Store 数据管理器
  • Dispatcher 事件分发器,用于派发Action
  • Action 动作,用于交互或其他View层操作

则比较清晰的定义了一个范式,即Flux程序应该怎么去写,Store的更新只能是通过Dispatcher去操作,然后Store再通知View层渲染新视图。

不过Flux太老了,也没有必要重新开始学,找一个demo琢磨一下,搞清楚整个工作流程,为Redux学习做好铺垫就行了。

这里我选择的demo是:GitHub - ruanyf/extremely-simple-flux-demo: Learn Flux from an extremely simple demo

关于工程结构:

image.png MyButtonController就是页面展示的内容。

MyButton是一个Component,内部放了一个ul标签和button标签,点击button的时候,往ul里边增加一个item。

button的点击事件:

var MyButtonController = React.createClass({
  getInitialState: function () {
    return {
      items: ListStore.getAll()
    };
  },
  // 组件挂载的时候注册监听
  componentDidMount: function() {
    ListStore.addChangeListener(this._onChange);
  },

  // 组件卸载的时候解绑监听
  componentWillUnmount: function() {
    ListStore.removeChangeListener(this._onChange);
  },

  // 监听回调,重新setState触发视图的重新渲染
  _onChange: function () {
    this.setState({
      items: ListStore.getAll()
    });
  },

  // 点击事件,触发ButtonActions的addNewItem纯函数调用
  createNewItem: function (event) {
    ButtonActions.addNewItem('new item');
  },

  render: function() {
    return <MyButton
      items={this.state.items}
      onClick={this.createNewItem}
    />;
  }

});

ButtonActions中定义了种种action,每一个action都是一个函数

var ButtonActions = {

  addNewItem: function (text) {
    AppDispatcher.dispatch({
      actionType: 'ADD_NEW_ITEM',
      text: text
    });
  },

};

addNewItem转发了一个对象给AppDispatcher的dispatch方法,这个对象包含两个内容,一个是action的类型,另一个是action传递的内容。

var AppDispatcher = new Dispatcher();
AppDispatcher.register(function (action) {
  switch(action.actionType) {
    case 'ADD_NEW_ITEM':
      ListStore.addNewItemHandler(action.text);
      ListStore.emitChange();
      break;
    default:
      // no op
  }
})

AppDispatcher是一个全局的Dispatcher对象,Dispatcher的实现后面再分析,总而言之,这里往Dispatcher中注册了一个callback,后面再通过AppDispatcher分发时间的时候,switch到对应的actionType,就会走对应的case,在里边完成Store的更新,即:

ListStore.addNewItemHandler(action.text);
ListStore.emitChange();

我们再看ListStore的实现:

var ListStore = assign({}, EventEmitter.prototype, {
  items: [],

  getAll: function () {
    return this.items;
  },

  // 添加一条text
  addNewItemHandler: function (text) {
    this.items.push(text);
  },

  // 提交change事件
  emitChange: function () {
    this.emit('change');
  },

  addChangeListener: function(callback) {
    this.on('change', callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener('change', callback);
  }
});

ListStore是一个EventEmitter对象,在添加完一条text之后,APPDispatcher中还调用了emitChange方法,通过EventEmitter的能力,发送了一个change事件。

而在MyButtonController组件挂载的时候,往ListStore中添加了对change事件的监听,因此最终,调用到MyButtonController的_onChange方法,通过setState,完成视图的更新。

让我们再回到这张图:

image.png

在整个工作流程中:

View层点击事件触发了一个Action,Action将会通过Dispatcher进行分发,然后执行对应的逻辑,去更新Store中的数据,Store更新完了之后,触发View的setState,进行视图重新渲染。

EventEmitter

EventEmmiter是NodeJS中的一个模块,作用和EventBus类似。在上面的ListStore中,我们用到了EventEmitter的emit,on,removeListener函数:

EventEmitter/EventEmitter.js at master · Olical/EventEmitter · GitHub

;(function (exports) {
    'use strict';
    function EventEmitter() {}

    // Shortcuts to improve speed and size
    var proto = EventEmitter.prototype;
    var originalGlobalValue = exports.EventEmitter;

    function indexOfListener(listeners, listener) {
        var i = listeners.length;
        while (i--) {
            if (listeners[i].listener === listener) {
                return i;
            }
        }

        return -1;
    }
		// 支持通过别名调用某个函数
    function alias(name) {
        return function aliasClosure() {
            return this[name].apply(this, arguments);
        };
    }

    proto.getListeners = function getListeners(evt) {
        var events = this._getEvents();
        var response;
        var key;

        // Return a concatenated array of all matching events if
        // the selector is a regular expression.
        if (evt instanceof RegExp) {
            response = {};
            for (key in events) {
                if (events.hasOwnProperty(key) && evt.test(key)) {
                    response[key] = events[key];
                }
            }
        }
        else {
            response = events[evt] || (events[evt] = []);
        }

        return response;
    };

    /**
     * Takes a list of listener objects and flattens it into a list of listener functions.
     *
     * @param {Object[]} listeners Raw listener objects.
     * @return {Function[]} Just the listener functions.
     */
    proto.flattenListeners = function flattenListeners(listeners) {
        var flatListeners = [];
        var i;

        for (i = 0; i < listeners.length; i += 1) {
            flatListeners.push(listeners[i].listener);
        }

        return flatListeners;
    };

    /**
     * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
     *
     * @param {String|RegExp} evt Name of the event to return the listeners from.
     * @return {Object} All listener functions for an event in an object.
     */
    proto.getListenersAsObject = function getListenersAsObject(evt) {
        var listeners = this.getListeners(evt);
        var response;

        if (listeners instanceof Array) {
            response = {};
            response[evt] = listeners;
        }

        return response || listeners;
    };

    function isValidListener (listener) {
        if (typeof listener === 'function' || listener instanceof RegExp) {
            return true
        } else if (listener && typeof listener === 'object') {
            return isValidListener(listener.listener)
        } else {
            return false
        }
    }

    /**
     * 注册一个事件监听器
     */
    proto.addListener = function addListener(evt, listener) {
        if (!isValidListener(listener)) {
            throw new TypeError('listener must be a function');
        }

        var listeners = this.getListenersAsObject(evt);
        var listenerIsWrapped = typeof listener === 'object';
        var key;

        for (key in listeners) {
            if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
                listeners[key].push(listenerIsWrapped ? listener : {
                    listener: listener,
                    once: false
                });
            }
        }

        return this;
    };

    // 别名调用addListener函数
    proto.on = alias('addListener');

    // ......
  
  	// 
    proto.removeListener = function removeListener(evt, listener) {
        var listeners = this.getListenersAsObject(evt);
        var index;
        var key;

        for (key in listeners) {
            if (listeners.hasOwnProperty(key)) {
                index = indexOfListener(listeners[key], listener);

                if (index !== -1) {
                    listeners[key].splice(index, 1);
                }
            }
        }

        return this;
    };

    /**
     * Alias of removeListener
     */
    proto.off = alias('removeListener');

    /**
     * 提交一个事件,事件提交之后,所有监听该事件的回调都会被执行
     */
    proto.emitEvent = function emitEvent(evt, args) {
        var listenersMap = this.getListenersAsObject(evt);
        var listeners;
        var listener;
        var i;
        var key;
        var response;

        for (key in listenersMap) {
            if (listenersMap.hasOwnProperty(key)) {
                listeners = listenersMap[key].slice(0);

                for (i = 0; i < listeners.length; i++) {
                    // If the listener returns true then it shall be removed from the event
                    // The function is executed either with a basic call or an apply if there is an args array
                    listener = listeners[i];

                    if (listener.once === true) {
                        this.removeListener(evt, listener.listener);
                    }

                    response = listener.listener.apply(this, args || []);

                    if (response === this._getOnceReturnValue()) {
                        this.removeListener(evt, listener.listener);
                    }
                }
            }
        }

        return this;
    };

    /**
     * Alias of emitEvent
     */
    proto.trigger = alias('emitEvent');

    proto.emit = function emit(evt) {
        // 把参数提取出来
        var args = Array.prototype.slice.call(arguments, 1);
        return this.emitEvent(evt, args);
    };
		
    // ......
}(typeof window !== 'undefined' ? window : this || {}));

那从源码来看,其实EventEmitter做的事情就是提供事件总线的能力,这样在Store完成数据变更之后,通过发送对应的事件,就能触发View层中的Component去执行对应的setState完成视图的更新。

Dispatcher

需要注意Flux只是一个开发规范,所以Flux的一些实现,并不唯一,我们来看下Dispatcher的实现:

flux源码分析 - 简书 (jianshu.com)

import { _classCallCheck, invariant } from './util'

class Dispatcher {
  constructor() {
    _classCallCheck(this, Dispatcher)
    this._callbacks = {}; // 回调map
    this._isDispatching = false;  // 是否在派发action
    this._isHandled = {}; // 完成态
    this._isPending = {}; // 等待态
    this._lastID = 1; // 回调map序号
  }

  // 扔一个回调过去 
  register (callback) {
    const _prefix = 'ID_';
    var id = _prefix + this._lastID++;
    this._callbacks[id] = callback;
    return id;
  }

  // 根据id 删除回调字典某个方法
  unregister (id) {
    !this._callbacks[id] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.unregister(...): `%s` does not map to a registered callback.', id) : invariant(false) : undefined;
    delete this._callbacks[id];
  }

  // 当dispatch(action)时,会按照callback的register顺序依次触发
  // 当如果callback中调用了waitFor方法,则会优先处理具有相应id的callback
  // 但要注意得是 :当两个store互相等待时,会进入死锁状态,即等待的id即处于处理态(pending) 
  // 但也未完成(!isHandled),这个时候会抛出异常(开发环境) 生产环境则直接跳过
  waitFor (ids) {
    !this._isDispatching ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.waitFor(...): Must be invoked while dispatching.') : invariant(false) : undefined;
    for (var ii = 0; ii < ids.length; ii++) {
      var id = ids[ii];
      if (this._isPending[id]) {
        !this._isHandled[id] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.waitFor(...): Circular dependency detected while ' + 'waiting for `%s`.', id) : invariant(false) : undefined;
        continue;
      }
      !this._callbacks[id] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.waitFor(...): `%s` does not map to a registered callback.', id) : invariant(false) : undefined;
      this._invokeCallback(id);
    }
  }
  
  // 分发事件
  dispatch (payload) {
    !!this._isDispatching ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.') : invariant(false) : undefined;
    // js本身是单线程的,用一个成员变量来记录payload参数即可
    this._startDispatching(payload);
    try {
      // 遍历所有的callback
      for (var id in this._callbacks) {
        if (this._isPending[id]) {
          continue;
        }
        // 执行每一个callback
        this._invokeCallback(id);
      }
    } finally {
      this._stopDispatching();
    }
  }

  isDispatching () {
    return this._isDispatching;
  }

  _invokeCallback (id) {
    this._isPending[id] = true;
    // 因为_callbacks是一个map,通过id取出来的就是一个个的function,直接调用传入payload即可
    this._callbacks[id](this._pendingPayload);
    this._isHandled[id] = true;
  }

  _startDispatching (payload) {
    for (var id in this._callbacks) {
      this._isPending[id] = false;
      this._isHandled[id] = false;
    }
    this._pendingPayload = payload;
    this._isDispatching = true;
  }

  _stopDispatching () {
    delete this._pendingPayload;
    this._isDispatching = false;
  }

}

export default Dispatcher

总结

Flux的原理其实很简单,更多的是提供一种单向数据流的开发规范。