React.js: Mejorando la vista

Introducción

Desde hace un tiempo venia escuchando mucho revuelo sobre React.js, un framework desarrollado por la gente de Facebook e Instagram que, según palabras de sus adeptos, es la panacea en el manejo de las vistas de nuestra aplicación.

Hace poco, en la aplicación en la cual estoy trabajando, nos encontramos con un el problema de tener una gran lista de items, que se muestran en pantalla con una estructura compleja (Digamos, cada item implica mucho html). Esta lista ademas podía ser modificada de distintas maneras, mediante filtros, búsquedas, etc. Nada complejo, de base, pero cada actualización de la misma tenia un interesante impacto en el rendimiento de la aplicación. Especialmente en dispositivos móviles

Según lo que había leído y escuchado, esto parecía un buen trabajo para React, y de hecho, lo fue.

Haciendo que React se encargue de dichos items y filtros, mejoro considerablemente el rendimiento de esta sección. Y lo mejor es que no tuvimos que cambiar nada más que la parte especifica de la aplicación encargada de mostrar dichos items.

¿Y que hace, exactamente, React?

Veamos un poco más en detalle que es lo que permitio que React funcione tan bien.

En su presentación, el sitio web de React menciona tres items:

La V del MVC

React no hace más que tomar las riendas de nuestra UI. No le importa como estén estructurados nuestros datos, ni como los procesamos, ni como se comunica nuestra aplicación con su backend, ni nada más allá de la interfaz de usuario.
Esto implica que podemos, sin mucho esfuerzo, incorporarlo en nuestra aplicación, incluso si nos encontramos un estado avanzado en el desarrollo de la misma.
Solo hay que crear los componentes y hacer que se dibujen.

Alto rendimiento mediante el uso de un DOM virtual

El secreto del rendimiento de React, esta en lo que ellos llaman DOM virtual. En el caso de nuestra lista de Items anteriormente mencionada, React mantiene una representación virtual de la misma. Al intentar actualizarla, React compara que fue lo que cambio de la lista anterior respecto a la nueva y se encarga de actualizarla.

Un flujo de datos de una sola dirección más simple que el flujo de datos tradicional.

Los datos en React fluyen en una sola dirección. A diferencie de, por ejemplo, Angular donde actualizar el modelo afecta a la vista y actualizar la vista afecta al modelo.
Como vimos antes, React solo se encarga de la vista así que en realidad, no tiene integrado el manejo de modelos. Esto ayuda a que sea sencillo integrarlo con Angular, Ember, Backbone y casi cualquier framework js que anda dando vueltas.
La gente de Facebook, de hecho, implementa una arquitectura a la que llamaron Flux, para poder aislar la propagación de datos del modelo y los eventos que dispara la vista.

Uso de React

React nos provee una API para crear componentes. Un componente, básicamente, tiene esta forma:

var Hello = React.CreateClass({
  render: function() {
    return (
      <div>Hola Mundo!</div>
    )
  }
});

React.renderComponent(<Hello />, document.getElementById('container'));

Básicamente, estamos insertando el componente Hello dentro de un elemento con id “container”. El elemento se va a insertar como un div con el texto “Hola Mundo”.
Primero, notamos que la sintaxis no es 100% javascript (¿ves los tags dentro del código?), si no jsx. Usar jsx no es obligatorio, y de hecho podemos escribir lo mismo usando 100% js, en una sintaxis quizá no tan obvia ni natural:

var Hello = React.createClass({
  render: function() {
    return React.DOM.div(null, "Hola Mundo!");
  }
});
  
React.renderComponent(Hello(), document.getElementById('container'));

La magia de React, en realidad, entra en juego cuando alteramos el estado de nuestros componentes. Cada vez que cambiemos el mismo mediante el metodo setState, React redibujara nuestro/s componentes, optimizando todo el proceso.
La Api de componentes, nos permite interactuar con el estado, eventos de DOM, propiedades, etc.

  // reactComponents/List.js
  var List = React.createClass({
    getInitialState: function() {
      return {
        items: []
      }
    },
    
    updateItems: function(newItem) {
      this.setState({
        items: newItems
      }); 
    },

    render: function() {
      var itemsList = this.state.items.map(function(item) {
        return (<Item title="item.text" />);  
      });
      
      return (<ul> {itemsList} </ul>);
    }
  });

  var Item = React.createClass({
    render: function() {
      return (<li> {this.props.text} </li>);
    }
  });

  module.exports = List;
  // script.js

  var ListComponent = require('./react-components/List');

  var items = [{text: "Item 1"},{text: "Item 2"}, {text: "Item 3"}];

  var myListComponent = React.RenderComponent(<ListComponent />, document.getElementById('container'));

  items.push({text: "Item 4"});

  myListComponent.updateItems(items); // Se actualiza el DOM con el item 4

  myListComponent.updateItems(items); // No se actualiza nada

Cómo funciona

La magia de React se debe a su DOM Virtual. Todas las operaciones que hacemos sobre sus componentes no se insertan directamente en el DOM si no que se trabaja sobre una representación del mismo.
Mediante esta técnica, React nos ahorra la costosa manipulación del DOM, ya que ante cada posible actualización, React analizará primero qué es lo que cambio para finalmente actualizar el DOM real mediante una estrategia optima con el menor impacto posible, logrando así un rendimiento mucho mayor que el logrado mediante la manipulación directa.

En el ejemplo anterior, la segunda llamada a updateItems no cambia el estado del componente, por lo que no existe una actualización del DOM real. E incluso en el primer llamado, React crea una estrategia para actualizarlo que logra que únicamente, se actualice lo necesario para agregar el nuevo item

Integración con otros Frameworks

Como mencione anteriormente, una de las cualidades de React, es que solo se encarga de la vista, por lo que podemos, sin mucho esfuerzo, integrarlo con otros frameworks MV*, como Angular o Backbone.
Esto, ademas, permite que podamos probar React sin tener que hacer un gran replanteo en nuestra arquitectura (Salvo que queramos usar Flux) ya que podemos hacer una implementación sobre un componente especifico sin alterar ningún otro.

En Angular, por ejemplo, podemos crear una directiva que se encargue de hacer de proxy entre Angular y React.

  /**
   * myApp/angular-directives/reactList.js
   */
  
  // Asumimos que tenemos definido un componente listComponent en algun lado.
  var listComponent = require('../reactComponents/listComponent');

  myApp.directive('reactList', [function() {
    return {
      restrict: 'E',
      scope: {
        items: '='
      },
      link: function(scope, el, attrs) {
        var component = React.renderComponent(listComponent({
          items: scope.items
        }), el[0]);
      }
      
      scope.$watch('items', function(new, old) {
        component.updateItems(new);
      }, true);
    }
  }]);

Luego solo hace falta que usemos dicha directiva en algun template mediante <ReactList />

En Backbone, podríamos hacer algo parecido implementando el metodo View.render, asegurandonos que ante el evento ‘render’ del modelo, nuestra vista se encargue de actualizar el estado del componente.

Conclusión

Como siempre, la idea del post no es ser una guía a React. Primero, porque existen muchas cuestiones respecto a su uso e implementación que me faltan por resolver y ademas porque debido a su etapa de desarrollo, seguramente su API cambie en el corto plazo. Y ademas, la gente de Facebook hizo un excelente trabajo con la documentación del mismo como para que tenga algun sentido.
Tampoco recomiendo que sea implementado de la misma forma que mostre mediante el escueto ejemplo anterior, seguramente podes encontrar un workflow mucho mejor y consciso, pero preferí enfocarme en la simplicidad del mismo para integrarse a otras herramientas.

Por último, solo me queda mencionar que ya hay disponibles plugins de grunt y gulp para integrar jsx en nuestro workflow actual de construcción con poco esfuerzo :).

Links y referencias