Análisis y práctica de flujo

Análisis y práctica de flujo

origen:

Después de desarrollar el applet React durante casi un año, ha habido algunos cambios en la forma de pensar sobre el marco de gestión estatal del programa. Al comienzo del proyecto, se utilizó Mobx para la selección técnica. La razón fue que los estudiantes que participaron en la selección en ese momento pensaron que la gran cantidad de código de plantilla y la curva de aprendizaje de Redux no eran adecuadas. Esto no fue un problema bajo los antecedentes en ese momento (los miembros del proyecto eran todos clientes nativos. Yo nací, no tenía un conocimiento profundo de la pila de tecnología front-end, y el cronograma del proyecto era muy ajustado en ese momento), pero con el subsiguiente ajuste del equipo, comenzó un proceso largo y difícil de mantener todo el código del proyecto por una sola persona, porque el proyecto en sí es complejo. Desde entonces, mis sentimientos acerca de Mobx han cambiado un poco. Desde el principio, fue la decisión correcta renunciar a Redux y elegir Mobx sin ninguna razón Más tarde, descubrí que, considerando el escenario comercial, Mobx puede no ser una buena solución. Por supuesto, no he usado Redux en proyectos reales, y no puedo decir directamente cuál de los dos es mejor y quién es peor, por lo tanto, espero que desde una perspectiva personal, preste atención, aprenda y comprenda Redux nuevamente. y ver si Redux puede resolver los problemas actuales de Mobx.Algunos problemas no son solo para verificar si hay fugas, sino también para revisar la experiencia de mantenimiento durante más de medio año.

Para el estudio e investigación de Redux, principalmente tomamos como ejemplo el libro "Redux en acción".

Debido al front-end no profesional, muchas cosas aún tienen que comenzar con lo más simple, como Redux, debe mencionar Flux.

texto:

Ya sea que el front-end sea Web o Android, el concepto arquitectónico se ha desarrollado durante tantos años. Personalmente, creo que uno de los puntos centrales es cómo hacer que la relación entre la capa Vista y la capa Modelo sea clara y clara. A partir de esto , marcos de desarrollo como MVC, MVP y MVVM nacieron. El marco requerirá que escribamos qué código en qué parte. Por supuesto, la práctica de Android MVC tradicional y Web MVC es en realidad diferente, pero el concepto es el mismo.

imagen.png

Hay más de un diagrama conceptual de MVC. Como se muestra arriba, todas estas son variantes de MVC o MVC, pero en términos de especificaciones, esperamos no operar directamente la capa Modelo en la capa Vista, y los cambios en la capa Modelo vaya a través de la capa Controlador, activado, y luego notifique a la capa Vista que actualice la vista después de que cambie la capa Modelo.

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

而关于Flux的理念图:

imagen.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

关于工程结构:

imagen.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();
复制代码

Veamos la implementación de 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 es un objeto EventEmitter.Después de agregar un texto, APPDispatcher también llama al método emitChange y envía un evento de cambio a través de la capacidad de EventEmitter.

Cuando se monta el componente MyButtonController, se agrega un detector para el evento de cambio a ListStore, por lo que, finalmente, se llama al método _onChange de MyButtonController para completar la actualización de la vista a través de setState.

Volvamos a esta imagen de nuevo:

imagen.png

A lo largo del flujo de trabajo:

El evento de clic de la capa Vista desencadena una Acción, y la Acción se distribuirá a través del Dispatcher, y luego se ejecutará la lógica correspondiente para actualizar los datos en la Tienda. Después de actualizar la Tienda, el estado de la Vista se activará para volver a renderizar la vista.

EventEmitter

EventEmmiter es un módulo en NodeJS, similar a EventBus. En el ListStore anterior, usamos las funciones emit, on, removeListener de EventEmitter:

EventEmitter/EventEmitter.js en maestro · 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 || {}));
复制代码

Desde el punto de vista del código fuente, de hecho, lo que hace EventEmitter es proporcionar la capacidad del bus de eventos, de modo que después de que Store complete el cambio de datos, al enviar el evento correspondiente, el Componente en la capa View puede activarse para ejecute el setState correspondiente para completar la actualización de la vista.

Despachador

Cabe señalar que Flux es solo una especificación de desarrollo, por lo que algunas implementaciones de Flux no son únicas. Echemos un vistazo a la implementación de Dispatcher:

Análisis de código fuente de Flux: libro breve (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
复制代码

Resumir

El principio de Flux es realmente muy simple, y es más para proporcionar una especificación de desarrollo para el flujo de datos unidireccional.

Supongo que te gusta

Origin juejin.im/post/7086327389998809095
Recomendado
Clasificación