ReactJS: un enfoque diferente

En los últimos años el auge de las aplicaciones basadas en HTML5 y Javascript ha sido imparable, y si a este lo unimos que Javascript no ofrece un conjunto de librerías estándar que dicte una forma canónica de hacer las cosas (como sí ocurre en otras plataformas como .NET o Java), es natural que este auge haya venido acompañado de la aparición de muchas librerías y frameworks para desarrollar este tipo de aplicaciones.

logo-reactjs

ReactJS es otra librería más diseñada para ayudarnos a desarrollar aplicaciones SPA, con una fuerte carga de interactividad en el cliente gracias al uso de Javascript. Ha sido desarrollada por Facebook y está publicada como Open Source con una licencia Apache 2.0.

Lo interesante de ReactJS es que aporta un enfoque distinto al que podemos ver en otras herramientas como KnockoutJS o AngularJS de las que ya hemos hablado por aquí en alguna ocasión.

OJO: este es un post de contar cosas, no de ver código. Si estás buscando eso, te sugiero que consultes la documentación de ReactJS o busques algún tutorial. Si quieres comprender mejor la filosofía que hay detrás de ReactJS, tal vez este post pueda ayudarte.

Una cuestión de alcance

Lo primero que debemos saber de ReactJS es que se trata de una librería para desarrollar interfaces de usuario. No estamos hablando de un framework todo incluido como AngularJS, Durandal o Ember, sino más bien de algo similar a KnockoutJS, al menos en cuanto a alcance.

ReactJS nos permitirá desarrollar el interfaz de usuario de una aplicación web, la V en un patrón MVC o MVVM, pero no incluye cosas como routing, acceso a servicios rest, inyección de dependencias, sistema de módulos, etc. A cambio, tampoco nos impone muchas restricciones sobre la forma de usarla, por lo que podemos mezclarla con otras librerías para conseguir el resto de funcionalidades.

Es bastante frecuente ver ReactJS usado en combinación con otras librerías. Hay quien ha integrado AngularJS y ReactJS (por motivos que ya veremos más adelante), y es habitual ver soluciones basadas en ReactJS y Backbone, una mezcla especialmente interesante puesto que Backbone aporta todo lo que le falta a ReactJS y no tiene un sistema de vistas propio.

El problema que trata de resolver ReactJS, al igual que todas estas librerías (o partes de estos frameworks), es el de construir un interfaz de usuario rico que permita mostrar en pantalla información sobre un modelo e interactuar con él. Esto es algo que puede complicarse rápidamente cuando tenemos mucha información a mostrar sobre distintos controles y necesitamos mantener sincronizada la información mostrada con el estado de los controles cada vez que se produce algún cambio.

La corriente general

Si pensamos en los frameworks y librerías más utilizados, AngularJS, Ember, Knockout, Durandal, etc., todos tratan de resolver el problema aplicando el patrón Model-View-ViewModel y un sistema de databinding bidireccional que se encargue de sincronizar automáticamente la vista y el viewmodel. Para ello suelen contar con algún mecanismo para definir declarativamente esos bindings y ahorrarnos mucho código.

El problema de este enfoque es que resulta muy difícil saber de antemano las implicaciones que tiene cada cambio. Al responder a una acción del usuario se provocan cambios en el modelo, que se propagan a distintos «controles» cuyo valores pueden cambiar, provocando nuevos cambios en el modelo y así sucesivamente, convirtiéndose en la fiesta loca de los rebotes:

Flujo de información MVVM

Cualquiera que haya trabajado con AngularJS se habrá encontrado alguna vez con el error «10 $digest() iterations reached» que indica que los rebotes entre modelo e interfaz de usuario no consiguen estabilizarse.

Este comportamiento hace que sea más complicado razonar sobre el flujo de información dentro de la aplicación y predecir el orden en que se acabarán ejecutando las cosas.

El enfoque alternativo

El planteamiento que realiza ReactJS es completamente distinto. ReactJS utiliza un sistema de databinding unidireccional, en el cual los controles son generados a partir de la información del modelo, pero la actualización del modelo se realiza de forma explícita.

Para montar este sistema, cada componente (control) de ReactJS cuenta con un método render que es el responsable de generar el HTML correspondiente al componente. Durante este proceso de generación, es posible enganchar manejadores de eventos a los elementos HTML para detectar cambios y actuar sobre el modelo. Cuando se producen cambios que afectan al estado interno del componente, éste se vuelve a renderizar por completo, no sólo aquellas partes el componente que han cambiado.

A simple vista, esto puede parecer sumamente ineficiente, pero aquí entra en juego una de las características de ReactJS que además lo convierte en una librería extremadamente eficiente: el Virtual DOM.

En lugar de generar directamente elementos HTML y modificar el DOM, que es una operación muy lenta, los componentes de ReactJS generan (renderizan) un modelo del DOM en memoria. Una vez generado el Virtual DOM completo, ReactJS se encarga de buscar las diferencias entre el DOM real y el virtual y realizar únicamente las modificaciones necesarias sobre el DOM real.

Esto implica un mayor consumo de memoria porque hay que mantener el Virtual DOM, pero generalmente no suele ser un problema y la mejora de rendimiento lo compensa. De hecho hay casos en los que compensa integrar este modelo con otros frameworks como AngularJS por la mejora de rendimiento que se consigue.

Para acabar de cuadrarlo todo, ReactJS mantiene una distinción muy clara entre dos tipos de información, las propiedades (props) del componente y el estado (state) del componente.

Las propiedades del componente están formadas por información que es inmutable para el componente. Esta información se recibe a través del objeto props en el momento de construir el componente y el componente puede asumir que no será modificada en ningún momento durante su ciclo de vida.

Se trata de información que el componente no «posee», por lo que será otro componente el responsable de modificarla. Cuando esa modificación se produzca, el dueño de la información reconstruirá nuevamente todo su Virtual DOM asociado, incluyendo sus componentes hijos que serán creados desde cero con la nueva información. Es por ello que podemos asumir que durante el ciclo de vida de un componente, es decir, desde que se crea hasta que se destruye, esta información es inmutable.

El estado del componente se almacena en la propiedad state y está formado por aquella información que sí es gestionada y modificada directamente por el componente. Cada vez que se realiza un cambio en el estado del componente como respuesta a una acción del usuario (o a algún otro factor externo), ReactJS se encarga de renderizar nuevamente el componente por completo. Puesto que un componente puede contener otros componentes, en el proceso de renderizado se volverán a crear estos componentes hijos con la nueva información. Es posible (y habitual) que el estado mutable de un componente sean propiedades inmutables de sus componentes hijos.

En el siguiente esquema podemos ver cómo se genera la jerarquía la componentes y cómo se propaga la información de unos a otros:

Flujo de información reactjs

La información siempre fluye de componentes padres a componentes hijos, un componente hijo no puede modificar directamente el estado de un componente padre. Esto hace que sea más fácil razonar sobre el comportamiento general de la aplicación y las implicaciones de cada cambio realizado sobre el modelo y sobre el DOM.

Hay muchas situaciones en las que tenemos que notificar cambios en un componente hacia su padre. Para esos casos la solución es simple: incluir dentro de las propiedades inmutables que recibe el hijo al ser creado una función callback del padre que será invocada en el momento oportuno. De esta forma tratamos todo de forma homogénea y la función callback no es más que otro elemento más de las propiedades inmutables que recibe el componente al ser creado. Esto además nos ahorra recurrir a otro tipo de mecanismos, como eventos, con los cuales es más complicado seguir el flujo de ejecución.

A priori esta distintición entre estado mutable e inmutable puede parecer confusa, pero si lo piensas un poco es bastante más clara que otros sistemas como el anidamiento/aislamiento/copia de $scopes que hace AngularJS para solucionar el mismo problema.

Conclusiones

Como decía antes no soy ningún experto en ReactJS, así que no veas estas conclusiones como una verdad absoluta, sino como mi opinión personal en este momento sobre esta librería.

El alcance limitado de ReactJS puede ser un punto a favor o en contra, dependiendo de lo que estés buscando. Cuando empecé a analizar AngularJS ya avisaba de que si hacías una aplicación con AngularJS, te casabas con AngularJS, y un tiempo después eso se acabó convirtiendo en un problema.

Sin embargo, si no quieres tener que buscar componentes por separado e integrarlos tú mismo, recurrir a soluciones tipo «todo incluido» puede ser una buena salida, aunque hay que valorar los riesgos de ambas opciones (que los tienen).

En mi opinión, utilizar librerías específicas para cada responsabilidad de la aplicación es una buena estrategia a medio plazo, ya que permite cambiarlas por separado en caso de ser necesario y evita que acabes acoplando todo tu código a la filosofía de una librería concreta.

Esto es algo muy natural en plataformas como .NET, donde el acceso a datos te lo proporciona una librería, la capa de presentación otra, la inyección de dependencias otra, y así sucesivamente, pero en Javascript parece que hay una tendencia mayor a buscar la solución todo incluido.

El enfoque que tiene ReactJS para la gestión de la información en la vista me parece muy atractivo. Durante mucho tiempo utilicé un enfoque similar en aplicaciones de escritorio aplicando variaciones del patrón MVP (Model-View-Presenter) y siempre me resultó más fácil de seguir que el código basando en eventos y rebotes del patrón MVVM, aunque no cabe duda de que en esto hay un componente personal importante.

Además, con el tiempo he ido apreciando cada vez más el valor de la inmutabilidad y lo mucho que ayuda a razonar sobre un sistema, por lo que la separación que hace ReactJS entre lo que podemos considerar inmutable y lo que podemos considerar mutable en cada contexto (componente) me parece un acierto.

En próximos posts seguramente profundizaré un poco más en ReactJS y lleguemos a ver algo de código, pero como siempre, prefiero sentar las bases antes en empezar a trastear.

10 comentarios en “ReactJS: un enfoque diferente

  1. Hola Juan María

    Hace muchos años, uno de mis profesores en Penn State Univ. me comentó que -incluso en la OOP- se deberían distinguir entre propiedades mutables e inmutables. Y que la forma «canónica», ideal si quieres, de diseñar una clase, sería que todas las propiedades inmutables quedaran totalmente resueltas en la llamada al constructor. También distinguía entre propiedades dependientes (de otras) e independientes, sugiriendo que las dependientes fueran siempre de solo lectura o modificables como consecuencia de un cambio en su dependencia. Incluso escribí un pequeño artículo sobre el tema, que después puse en español para explicarlo en mis clases sobre .NET.

    Por eso me ha gustado este enfoque. Enhorabuena por tu blog, y felices fiestas.
    Marino

  2. Hola Marino,

    Gracias por el comentario, coincido bastante con esa visión. Por cierto, ¿El artículo que escribiste está disponible en algún sitio? Suena interesante.

    Con respecto a OOP e inmutabilidad, yo creo que el problema es que los lenguajes más populares, por defecto, fomentan que todo sea mutable.

    Eso hace que haya que seguir «convenciones» o «reglas» como las que describes para mantener un mínimo de coherencia en el código.

    En lenguajes funcionales se hace (a mi parecer) un tratamiento más sensato del tema.

    O bien todo es inmutable, como en el caso de Haskell y demás lenguajes puros, o bien por defecto las «variables» son inmutables y hay que declararlas explícitamente como mutables, como en el caso de Ocaml y F#, o el lenguaje cuenta con primitivas específicas para tratar la mutabilidad de forma segura, como es el caso de Clojure con sus atoms, refs y STM.

    Un saludo,

    Juanma.

  3. Boris Matos dijo:

    Que increíble encontrar info sobre React.js en español!, gracias por el post, muy interesante. Yo también haré mi propia contribución a la comunidad en español creando un blog exclusivo para hablar sobre React.js :)

  4. Genial :D Marino, ¿podrías compartir el post?, me parece interesante lo que planteo tu profesor; En cuanto a ti Juamma, de nuevo gracias por compartir tus conocimiento, y concuerdo contigo sobre AngularJS.

  5. Muy bueno, me sirvió para entender en que se está usando esta librería. Personalmente AngujarJS la encuentro molesta e inútil, todo lo que hace se puede hacer por acceso directo al DOM usando los cssClass, id, data-name etc etc. Es una librería bastante molesta e inútil que usan otras librerías y que de no ser por eso ni la mencionaba. ReacJS parece más interesante, aunque las tareas que realiza nuevamente parecen reemplazables. Si voy a almacenar en memoria elementos del DOM para acelerar la visualización, no necesito otra librería que consuma recursos de memoria y tiempo de proceso, me bastaría con tener una variable de acceso global con atributos de tipo estático para almacenar los elementos del DOM a los que estoy frecuentemente haciendo acceso con sus correspondientes propiedades y funciones necesarias, encapsular la lógica en AngularJS innecesario sin considerar los dolores de cabeza para seguir los cambios de estado, usar ReacJS parece más lógico, pero si hará algo que ya puedo hacer con usar un template de clase o una variable global que haga la orquestación con funciones, etc… , no se que.

    Respecto al amigo que estudio en el Penn State y que saca a colación el dato para comenzar a opinar le diré que su profesor deberá volver a estudiar, porque en OOP se puede declarar la inmutabilidad de un objeto indicando que es static, const, o lisa y llanamente no incluyendo la funcion «set» o declarandola «private», «internal», «protected»… etc etc… segun el lenguaje… pero declarar elementos de memoria inmutable no tiene mucho sentido, ya que la memoria se hizo para ser modificada y son las etiquetas de accesibilidad las que se usan para declarar la inmutabilidad… pero aun asi, en los lenguajes funcionales se tiene la «inmutabilidad» por defecto en algunos casos, en otros se declara, pero para ser francos, con las máquinas actuales, la diferencia entre el tiempo de binding en memoria, entre una variable inmutable y una que mutable, es casi despreciable… solo aplicable a enormes sistemas, con gran cantidad de usuarios y donde, las palabras «const» y «static» hacen en resumen la misma tarea.

  6. Bravo por tu artículo, me ha encantado.
    Además de ser mago trabajo como desarrollador (me llaman consultor, pero no es verdad, soy demasiado friki para que me lo consideren)
    Yo no soy puritano en prácticamente nada, debe ser por mi naturaleza. Y aunque no profundizo en cada materia intento ponerle un poco de cordura a lo que hago. He estado haciendo pequeños desarrollos en un banco con AngularJS dentro de SharePoint y mis compañeros y yo hemos acabado odiándolo (bueno, no tanto) Al final me pasé a Backbone y estoy muy contento, prefiero tener el dominio de todo lo que ocurre y aún siendo mago, prefiero que las cosas no ocurran «mágicamente» Lo más seguro use ReactJS y se lo añada a BackboneJS.
    Lo dicho, gracias por tu blog y tu trabajo.

Comentarios cerrados.