Poor Man's Flux

Flux, no es más que la arquitectura de aplicaciones que utiliza Facebook en sus interfaces web. Complementa perfectamente la filosofía de componentes reutilizables utilizando un flujo de datos unidireccional. Dicho así no se capta de inicio la idea, vayamos más a fondo. Ojo, Flux NO es una librería, es más bien un patrón de como hacer aplicaciones.

Las Aplicaciones Flux tienen 3 partes esenciales: Dispatcher, Stores y Views (Siendo esta última no más que componentes React). No se parece en nada al patrón Modelo-Vista-Controlador, en Flux se hace más evidente el flujo unidireccional de los datos para poder gestionar los estados de nuestra aplicación.

Cuando un usuario interactua con un Componente React (View), este a través de un Dispatcher indicará la ejecución de una acción que una vez terminada modificará el Store donde se almacenan los datos centralizados y de ahí nuestro componente View nuevamente se refrescará actualizando su contenido.

Stores es un repositorio centralizado del estado de nuestra aplicación, en teoría podemos tener tantos como sean necesarios. En la práctica a veces es conveniente contar con tantos Stores como procedencias de los datos que contienen.

Los datos en una aplicación fluyen en un solo sentido.

Una View puede causar que acciones sean ejecutadas en cualquier momento. Este esquema hace que sea sencillo de razonar el flujo de datos de nuestra aplicación. Evitandose con esto una marea de callbacks y eventos para actualizar secciones independientes con todos los inconvenientes que resultan.

Dispatcher

El Dispatcher es un componente que se encarga de registrar que acciones están disponibles, si fuera necesario de hacer un registro de las mismas y encadenar las acciones y datos que pasan por el mismo.

Existen muchas variaciones de estos, con mayor o menor nivel de funcionalidades.

Stores

Stores tienen un rol similar al Modelo en el patron MVC. Almacenan todo el estado de nuestra aplicación. En la medida que sepamos modelar estos estados y almacenarlos podremos brindar funcionalidades extras a nuestros usuarios. Un Store almacena el estado de nuestra aplicación en un momento del tiempo determinado, por lo que implementar mecanismos de undo se hace relativamente sencillo si podemos almacenar ademas los estados anteriores.

Existe una relacion muy cercana entre el Dispatcher y los Stores ya que los primeros deben saber a donde enviar los datos de las acciones que reciben.

Views, Controllers (Componentes)

Con React tenemos a nuestra disposición un mecanismo ideal para representar las llamadas Views, solo que en React le llamamos Componentes. Además es posible crear una clase de componentes de primer nivel que son los que reconocen los cambios en el estado de la aplicación a través de escuchas en los Stores y son capaz de actualizar su estado y el de los componentes hijos de este.

Cuando un Componente-Controlador recibe una actualizacion de estado de un Store este puede inmediatamente ejecutar el metodo setState para modificar su estado y así el de los hijos del mismo, forzando que se actualice la aplicación con los cambios asociados a la acción que se ejecutó (sí los hubiere).

Actions

Las Actions son funciones que se invocan a través del Dispatcher, ademas pueden tener datos asociados. Ej: En una aplicación de mensajería la acción podría ser enviarMensaje(texto).

Estas pueden invocarse desde cualquier punto de nuestra aplicación. Básicamente son la unica forma de cambiar el estado de la aplicación. Cualquier otra acción que intente sobrepasar esta restricción estaría rompiendo el paradigma Flux.

Demo 1

Existen muchísimas librerías para hacer Flux en Aplicaciones Web, una de las más utilizadas es Alt (Es además la que utilizamos en TES), otras opciones incluyen:

Para este ejemplo no utilizaremos ninguna de las anteriores. Resulta que seguir el paradigma es simple por lo que nos haremos nuestra propia implementacion de Flux a la que llamaremos Poor Man's Flux.

Poor Mans' Flux

PMF es simple. Se compone unicamente de un mixin. En React un mixin es una función que nos permite mezclar funciones del ciclo de vida de un componente React para extender de esta forma otros componentes. Es una forma de herencia múltiple muy básica.

El mixin como tal es como sigue:

poorMansFluxMixin = function(store, actions) {
  return {
    getInitialState: function() {
      return store;
    },
    
    childContextTypes: {
      flux: React.PropTypes.object
    },

    getChildContext: function() {
      var
        flux;

      flux = { store: this.state };
      flux.actions = actions(this.dispatch, flux);

      return {
        flux: flux
      };
    },

    dispatch: function(data) {
      this.setState(data);
    }
  };
};

Como ven el mixin redefine varios métodos de un componente React: getInitialState, childContextTypes, getChildContext y además crea una función dispatch

Lo podemos usar en un componente de alto nivel para que el estado del mismo sea nuestro Store. la funcion dispatch no hace mas que cambiar el estado a traves de funciones que especificaremos en una función actions.

Veamos un ejemplo con dos Botones:

'use strict';

var
  poorMansFluxMixin,
  Button,
  App,
  myActions;


poorMansFluxMixin = function(store, actions) {
  return {
    getInitialState: function() {
      return store;
    },
    childContextTypes: {
      flux: React.PropTypes.object
    },

    getChildContext: function() {
      var
        flux;

      flux = { store: this.state };
      flux.actions = actions(this.dispatch, flux);

      return {
        flux: flux
      };
    },

    dispatch: function(data) {
      this.setState(data);
    }
  };
};

Button = React.createClass({
  propTypes: {
    pressed: React.PropTypes.number.isRequired
  },

  render: function() {
    return (<button className="btn btn-default" onClick={this.increase}>{this.props.pressed} {this.props.children}</button>);
  },

  increase: function(evt) {
    if (this.props.doA) {
      this.context.flux.actions.increaseNumberA();
    }

    if (this.props.doB) {
      this.context.flux.actions.increaseNumberB();
    }

    evt.preventDefault();
  },

  contextTypes: {
    flux: React.PropTypes.object.isRequired
  }
});

myActions = function(dispatch, flux) {
  return {
    increaseNumberA: function() {
      dispatch({valueA: flux.store.valueA + 1});
    },
    increaseNumberB: function() {
      dispatch({valueB: flux.store.valueB + 1});
    }
  };
};

App = React.createClass({
  mixins: [poorMansFluxMixin({valueA: 0, valueB: 0}, myActions)],

  render: function() {
    return (
      <div>
        <Button pressed={this.state.valueA} doA>Button A</Button>
        <Button pressed={this.state.valueB} doB>Button B</Button>
      </div>
    );
  }
});

React.render( <App/>, document.getElementById('container'));

Ver en JSBin

Como ven nuestro componente App esta decorado con el mixin poorMansFluxMixin donde se le indica el estado inicial de nuestro Store, conteniendo dos variables (valueA y valueB) y ademas la función de acciones (increaseNumberA e increaseNumberB)

En la interfaz se visualizarán dos botones cada uno llama a las acciones A y B y los valores de estos en el Store se modifican adicionandole +1 cada vez.

Demo 2, TODOList

Recuerdan el componente TODOList de un post anterior, pues bien, hagamoslo Flux.

var
  TodoItems,
  TodoInput,
  TodoList,
  todoActions,
  poorMansFluxMixin;

poorMansFluxMixin = function(store, actions) {
  return {
    getInitialState: function() {
      return store;
    },
    childContextTypes: {
      flux: React.PropTypes.object
    },

    getChildContext: function() {
      var
        flux;

      flux = { store: this.state };
      flux.actions = actions(this.dispatch, flux);

      return {
        flux: flux
      };
    },

    dispatch: function(data) {
      this.setState(data);
    }
  };
};

todoActions = function(dispatch, flux) {
  return {
    addTodo: function(item) {
      dispatch({todos: flux.store.todos.concat([item])});
    }
  };
};

TodoList = React.createClass({
  mixins: [poorMansFluxMixin({todos: []}, todoActions)],

  render: function() {
    return (
      <div>
        <h3>TODO List</h3>
        <TodoItems items={this.state.todos}/>
        <TodoInput addTodo={this.addTodo}/>
      </div>
    );
  }
});

TodoItems = React.createClass({
  propTypes: {
    items: React.PropTypes.array.isRequired
  },

  render: function() {
    var
      createItem;

    createItem = function(item, index) {
      return (
        <li key={index}>{item}</li>
      );
    };
    return <ul>{this.props.items.map(createItem)}</ul>;
  }
});

TodoInput = React.createClass({
  getInitialState: function() {
    return {item: ''};
  },

  onChange: function(e) {
    this.setState({
      item: e.target.value
    });
  },

  handleSubmit: function(e) {
    e.preventDefault();
    this.context.flux.actions.addTodo(this.state.item);
    this.setState({item: ''}, function() {
      React.findDOMNode(this.refs.item).focus();
    });
  },

  render: function() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input ref="item" type="text" onChange={this.onChange} value={this.state.item}/>
        <input type="submit" value="Add"/>
      </form>
    );
  },

  contextTypes: {
    flux: React.PropTypes.object.isRequired
  }
});

React.render(<TodoList />, document.getElementById('container'));

Ver en JSBin

Flux, como concepto es muy simple. Existen muchas librerías para utilizarlo, el debate aun no termina, muchas desarrolladores estan adoptandolo y cada uno aporta un granito de arena a definir el futuro de este paradigma.

El código fuente de los ejemplos los pueden encontrar en: https://github.com/ernestofreyreg/poormansflux

Comments

comments powered by Disqus