El costo de usar Redux

¿Cuál es el costo de usar Redux? Lo importante es comparar las ventajas y desventajas de usar una tecnología en un contexto dado.

El costo de usar Redux

Redux es un patrón de manejo de estado para aplicaciones React. Aunque se promociona agnóstico de la librería de vistas, Redux nace explícitamente a partir de las necesidades del equipo de Facebook ante los problemas de manejar estado en aplicaciones React. Como patrón, Redux no es nada nuevo, está basado en la arquitectura de Elm y muchas de las ideas que implementa se pueden llevar tan atrás como hasta la Programación Funcional Reactiva; pero siendo descendiente de los valientes señores feudales detrás de Facebook, su popularidad se vio prontamente impuesta.

El problema que soluciona no es difícil de entender: tengo una aplicación React, la misma tiene componentes que encapsulan interfaz y/o comportamiento dispuestos en un árbol inmenso, y una hoja lejana de la raíz (¿Un botón de login quizás?) necesita actualizar un estado que repercute en varios componentes desperdigados por el árbol. ¿Tengo que pasar props hacia abajo indefinidamente? Por suerte, no.

Redux basicamente propone algo así: guardá todo tu estado en una variable global (store), editalo (reducers) ordenadamente (actions + dispatch), y propagá esos cambios (connect). De yapa te queda todo ordenado, y te doy un debugger con viaje en el tiempo y trazabilidad.

Personalmente, siempre lo miré con cierto escepticismo. Vi aplicaciones funcionar exitosamente sin nada de todo esto, y seguramente eran mejorables pero la solución estaba en un refactor o dos. Además, como toda moda impuesta, siempre surge la duda de si es la decisión correcta, lo que sea que eso signifique. ¿Y qué significa?

Para mí la decisión correcta no existe, aunque podemos aproximarnos, y lo importante es comparar las ventajas y desventajas de usar una tecnología en un contexto dado. Las ventajas las conocemos gracias a mil posts de Medium. Pero entonces, urge la pregunta: ¿Cuál es el costo de usar Redux?

El lenguaje

Los lenguajes funcionales soportan hace décadas un estilo de programación que es orientado a estructuras de datos. Por ejemplo, en los lenguajes de la familia de LISP, tenemos docenas de funciones nativas que operan sobre estructuras de datos, transformándolas, combinandolas, filtrándolas, etc. Los lenguajes funcionales manejan la Santa Trinidad de funciones de alto orden sobre listas, tan populares ahora, que son map, filter y reduce, desde mucho antes de que JavaScript las adopte. Esto se traduce a maneras prolijas, predecibles, documentadas y performantes de manipular estructuras de datos complejas, muchas veces inmutablemente.

Ejemplo de operaciones sobre estructuras de datos primitivas en Clojure.

Redux es un patrón que se inspira en esta manera de programar, ¡Pero que intenta operar en el entorno de un lenguaje orientado a objetos! En el 2020, JavaScript soporta algunas operaciones inmutables básicas sobre listas y objetos nativamente, pero esto es reciente y siempre terminamos dependiendo de alguna librería externa como Ramda o Lodash, lo que se traduce en toda la complejidad asociada a las dependencias externas que ya conocemos (sumada a la complejidad nativa a JavaScript que no tiene muy en claro cómo manejar dependencias tampoco).

Vale mencionar que, aparte, no es la manera idiomática de programar en JavaScript, y menos en React. React es mucho más parecido a la programación orientada a objetos (bajo la equivalencia objeto ⇔ componente, y la premisa del estado encapsulado) que a como se programaría una interfaz en un lenguaje funcional.

Esto, en mi opinión, es una fricción constante al momento de usar el patrón Redux en JavaScript.

La implementación

La implementación de Redux, específicamente React-Redux, es hiper-compleja. La API tiene una superficie inmensa, repleta de conceptos abstractos, funciones de alto orden, conceptos de React mezclados, reglas implícitas e inchequeables (¡No mutes! ¡No tengas side-effects!), etc. Para el ejemplo mínimo necesitamos un store, varios reducers, una manera de combinarlos, actions para mutar el estado, dispatch para correr los mismos, connect para recibir los cambios, mapStateToProps para transformarlos, y seguro me olvido de algo.

Es cierto que la documentación dice que todo esto es opcional, que en el corazón Redux es muy sencillo y no necesitamos todo esto, pero la realidad es que cuando una librería nos presenta la opción de separar responsabilidades, ejercemos nuestro derecho a la prolijidad.

Y no termina ahí. La documentación es inmensa. Desde patrones para asincronismo, middlewares, hasta una sección entera dedicada a cómo estructurar reducers, ¡Incluyendo irónicamente una manera de simular estado mutable con helpers de inmutabilidad!

En la práctica, nos enfrentamos a una docena de funciones sueltas sin dominio o imagen claros (JavaScript no es tipado, y los sistemas de tipos que le podemos agregar son ad-hoc, incompletos y defectuosos); nos vemos obligados a leer detalles de implementación una y otra vez, incapaces de delegar en el comportamiento encapsulado de un objeto; navegando un stack de funciones intermedias producto de cientos de helpers y funciones de utilidad aisladas; todo esto en el contexto de un bundle transpilado, con sourcemaps aproximados y disfuncionales, plugins desconfiables, undefineds y estado mutable.

¿Ya extrañan Visual Basic?

La realidad

Siempre que desarrollamos software tomamos decisiones, y estas decisiones las hacemos en un contexto específico: los detalles del proyecto, la experiencia y composición del equipo, la accesibilidad a documentación, las librerías disponibles, el tooling accesorio, la realidad material y social del momento, e infinitas otras variables.

Y estas decisiones tienen costos y beneficios.

Redux trae beneficios muy valiosos. Por ejemplo, centralizar la mutación del estado nos da la posibilidad de tener código amistoso con la cache, un beneficio de performance inmensurable en caso de tener ese tipo de problemas. El orden que impone puede resultar beneficioso también en una aplicación inmensa o con múltiples partes acopladas. El tooling disponible, como el time-travel debugger, puede ser muy útil. Y ni hablar del control absoluto y total que nos da del estado.

Pero también, Redux como patrón y como librería de JavaScript, tiene un costo muy alto, y hay que sopesar eso antes de agregarlo a un proyecto.

La alternativa

En la próxima entrada, voy a presentar un patrón que estamos usando en varios lados que combina la API de Contexts de React y componentes de alto orden para lograr una manera sencilla y práctica de propagar cambios de estado globales.

Porque la teoría sin praxis es un placer burgués.

¡Nos vemos!