Table of Contents
Redux es una librería que te ayuda a gestionar el estado de tu app. En este artículo, desmitificando Redux, no vamos a debatir el porqué usarlo y el cómo usarlo de forma correcta, sólo nos centraremos en como crear nuestra propia implementación acercándonos al resultado final sin tener que cubrir todos sus aspectos, detalles y herramientas. El objetivo es entender cómo funciona a grandes rasgos para poder eliminar la mayor parte de misterio que hay detrás de esta simple librería.
Nuestro ejemplo de desmitificando Redux se basa en un listado de jugadores de Korfbal los cuales vamos a poder añadir como convocados en el partido de la próxima semana. Es el típico ejemplo TODO pero con un toque personal 😉
Para poder ver todas las partes, se ha creado un repositorio con diferentes ramas en las que podemos observar y entender los diferentes bloques o secciones por partes.
- Setup: Github link
- Init Store: Github link
- Actions: Github link
- Reducers: Github link
- Store subscriptions: Github link
- Visualization: Github link
Desmitificando Redux: Setup
Para poder empezar con nuesto tema: desmitificando Redux, usaremos Webpack para que nos configure todo el entorno y nos sirva mediante devServer el html en el puerto 3000, además de usar el módulo ATL para poder cargar los archivos Typescript. Crearemos además un package.json el cual se encargará de exponernos el script start así como las dependencias necesarias para el proyecto.
Para mas información:
- https://webpack.js.org/configuration/dev-serve
- https://github.com/s-panferov/awesome-typescript-loader
Partiremos de un archivo index.html que nos va a mostrar la cantidad de jugadores que tenemos y un input con un botón para añadir nuevos jugadores.
Crearemos una interfaz de jugador y la usaremos para tipar nuestros jugadores. No usaremos clases y builders para este articulo, solo objetos planos tipados.
src/models/Player/entity.ts
Y finalmente y antes de entrar en materia, para iniciar nuestro ejemplo crearemos un archivo app.ts con un selector del botón «añadir jugador» y otro selector de nuestro input. Seguidamente registraremos un evento al botón para que, cuando se haga click, y con una pequeña validación, nos imprima por consola un objeto jugador y haga reset del input.
src/app.ts
Con todo esto configurado y creado, podemos iniciar el proyecto instalando las dependencias y ejecutando el comando start en consola
Ahora ya deberíamos poder abrir el navegador, ir a apiumhub.com:3000, poder ver nuestro html y al hacer click en el botón poder ver nuestro jugador con el estado de su convocatoria por la consola del navegador.
En nuestro ejemplo, podemos ver un tercer parámetro de tipo boolean en nuestro event listener. Mi experiencia me dice que no es muy común que la gente conozca este parámetro «useCapture» así que os dejo un artículo que lo explica muy bien. https://javascript.info/bubbling-and-capturing
Desmitificando Redux: Init Store
Ahora si, empecemos con el contenido importante de este artículo.
Iniciamos creando nuestro Store.
src/store/store.ts
src/models/store.ts
Como podemos observar, es una clase que va a contener básicamente un array de suscriptores al Store, va a gestionar nuestra lista de Reducers y además tendrá un estado, el qual no es más que un objeto plano.
El primer argumento del constructor serán los reducers y el segundo un estado inicial, y haremos que los dos sean un objeto vacío por defecto.
Para poder crear instancias de nuestro Store, haremos un binding del estado inicial al estado del store dentro del constructor.
Por último, como necesitaremos preguntar por el estado de nuestra aplicación en cualquier momento, gracias a la propiedad get de Typescript crearemos un getter para acceder al value del estado que se ha definido como privado.
Para poder ver nuestro primer paso, vamos a crear un archivo Barrel dentro de la carpeta store que exportará todo el contenido de nuestro Store, importaremos este archivo con el nombre fromStore en el app.ts y crearemos una nueva instancia del Store con un objeto vacío como reducers y un estado por defecto.
Finalmente haremos un console.log del valor del estado de la app.
src/app.ts
Si refrescamos el navegador, deberíamos ver el estado inicial que hemos definido por consola.
Desmitificando Redux: actions
Vamos a crear nuestra primera acción para que actualice el estado del store al ser ejecutada desde el botón «añadir jugador».
Una acción siempre tiene un type de tipo string y puede tener también un payload, y para que pueda ser ejecutada, nuestro Store debe tener un método llamado por convención «dispatch» que recibe dicha acción como parámetro.
src/models/store.ts
src/store/store.ts
De momento, harcodearemos el cambio del estado dentro del método dispatch hacia players ya que de este proceso se encargará el reducer que desarrollaremos mas adelante.
Para hacer el cambio del estado, se debe de hacer de forma immutable, y para ello vamos a generar un nuevo objeto que fusionará el estado anterior del estado con el nuevo estado de players usando el spread operator. También añadimos un console.log dentro del método dispatch para ver que el estado de la app se ha modificado correctamente.
Finalmente, en nuestro app.ts eliminamos el console log del estado inicial, cambiamos el console.log del método del event listener por la ejecución del método dispatch con un type y como payload el player.
src/app.ts
Ahora, deberíamos ver en consola que el estado se va modificando correctamente a medida que vamos añadiendo nuevos jugadores.
Desmitificando Redux: reducers
En esta parte, crearemos un reducer para players.
src/store/reducers.ts
src/models/Player/state.ts
Como es de imaginar, en el estado podemos tener múltiples propiedades. Hemos creado un estado inicial con la propiedad loaded que nos va a indicar si los jugadores han cargado (por ejemplo de una llamada http), y loading que nos indicará si se encuentra en ese proceso.
Estas dos propiedades no las vamos a usar ya que no tenemos ninguna llamada asíncrona que nos cambie este estado, pero nos sirve como ejemplo para entender un poco mas allá lo que nos proporciona esta gestión además de poder ver mas datos en consola. También crearemos la propiedad data que será un array donde almacenaremos todos los jugadores con el valor que pasábamos por parámetro a la instancia del Store para tener un dato de ejemplo.
La función playersReducer recibe un primer parámetro state que tiene de valor por defecto el estado inicial que hemos creado y como segundo parámetro recibe una acción que como ya hemos explicado tiene un type y un payload opcional. Dentro vamos a evaluar el type de la acción con un switch y como caso vamos añadir el type ‘ADD_PLAYER’ que estamos usando en el dispatch de la acción en el evento del botón. Componemos el nuevo estado de data con el payload que recibimos (en este caso el player) y finalmente retornamos el valor del estado modificado de forma immutable.
Ahora necesitamos registrar este reducer, y para ello exportaremos todo el contenido del archivo reducers.ts en el archivo Barrel y lo importaremos en el app.ts.
Creamos una constante llamada reducers la cual es un objeto con un item players asignado a su función reducer. Una de las partes interesantes cuando estamos aprendiendo el patrón de Redux es que podemos decir que queremos varios reducers que cambian diferentes partes del estado del Store.
Ahora podemos substituir el parámetro reducers de la instancia del Store por nuestro nuevo objeto reducers.
src/app.ts
En el constructor de nuestro store, hacemos el binding del primer parámetro a reducers y en el método dispatch podemos cambiar la modificación del estado por un método llamado reduce. No debemos confundirlo por el prototype reduce del Array, es una función propia de nuestra clase. Reduce es llamado con el estado actual del store y con la acción que se ha ejecutado.
src/store/store.ts
Lo que viene a continuación es una parte importante, así que mucha atención.
Primero recordemos nuestro objeto reducers:
src/app.ts
El método reduce se encarga de generar un objeto con las mismas claves que el objeto reducers pero asigna como valor a cada clave el valor de retorno de la función reducer asignada. Recordamos que esa función, en este caso específico playersReducer, dado un estado y una acción, evaluando la acción con un switch, nos devolvía o bien el estado modificado, o bien el estado sin modificar. El estado que se le pasa a playersReducer es el específico del iterador (es decir, el ultimo valor de la clave) para que no tenga acceso a los otros estados de nuestro Store. Obtenemos de esta forma, el nuevo estado de nuestro Store.
src/app.ts
Para finalizar esta parte, hemos substituido el binding del state en el constructor por una llamada a reduce con una acción vacía y hemos añadido un console.log del valor del estado de la app después del dispatch dentro del evento asignado al botón.
Desmitificando Redux: store subscriptions
Hasta ahora tenemos prácticamente acabado nuestro Store, pero no hemos hecho absolutamente nada con el DOM aún. No estamos actualizando sus elementos ni mostrando información.
Si vamos al store, veremos que hemos cubierto los reducers, que hemos cubierto el estado de la app pero no las suscripciones.
Como inicio, asignaremos un array vacio a las suscriptions en el constructor. Seguidamente, crearemos un método subscribe que nos permitirá recibir los cambios del store en cualquier punto de nuestra app. Este método básicamente lo que hará será recibir una función, añadirla como subscriber al array y llamar a cada uno de los subscribers pasandole el value del store, es decir, el estado de nuestra app. A este proceso de invocación le llamaremos notify.
Además, notificaremos a todos los subscribers cada vez que hagamos un dispatch, para que reciban el nuevo estado cada vez que éste se modifique.
src/store/store.ts
A continuación, eliminaremos el console.log del eventListener y haremos un subscribe dentro de nuestro app.ts, el qual haremos que como parámetro reciba una función que haga un console.log. Lo que le pasemos como parámetro es lo que ejecutará nuestro store cada vez que internamente llame a notify.
src/app.ts
Si refrescamos el navegador, veremos que se imprime por consola el estado inicial y que cuando añadimos un player, nuestro subscriber nos hace el console.log con el nuevo estado de la app.
Desmitificando Redux: visualización
Todo esto empieza a coger forma, pero para dejarlo un poco mas vistoso añadiremos comportamiento sobre el DOM y además crearemos un método unsubscribe con su correspondiente botón para que no tengamos memory leaks de suscripciones al store ya sea si cambiamos de escenario,, eliminamos el componente y lo volvemos a renderizar y un etcetera de casuísticas que nos pueden dar este problema.
Para empezar, crearemos una función renderPlayers que nos va a permitir poder añadir elementos de forma iterativa al DOM dentro del elemento ul con la clase «players».
Esta función se encarga de recibir por parámetro una lista de jugadores, añadir en el DOM el numero total de jugadores y además por cada uno de ellos añadir un list item con el nombre del jugador y un botón eliminar. Además cambiaremos la función que le pasamos por parámetro a subscribe por ésta nueva y asignaremos la suscripción a la variable unsubscribe.
No es la forma mas correcta de actualizar el DOM en términos de performance pero para nuestro ejemplo es suficiente.
La asignación de la suscripción a la variable “unsubscribe” nos permitirá quitar esta función del array subscriptions del Store para que cuando se ejecute el método notify() ésta ya no esté dentro y por lo tanto no se ejecute.
Para poder hacer esto, solo tenemos que retornar una función en el método subscribe que se encargue de esta tarea con un simple filter.
src/store/store.ts
Finalmente, para probar que nos podemos hace unsubscribe, crearemos un botón al final de nuestro html, y le añadiremos un evento que ejecute esta función.
src/app.ts
Con esto hecho, ya podemos ir al navegador, añadir jugadores, ver como se actualiza el DOM, hacer click en el botón “Unsubscribe“ y ver como el DOM deja de actualizarse aunque por consola podamos ver como el estado sigue cambiando.
En este punto, podemos decir que ya hemos finalizado nuestra implementación de Redux. Para completar nuestro ejemplo que os hemos mostrado: desmitificando Redux, deberíamos añadir mas evaluaciones de
acciones en nuestro reducer para que modifique el estado de la forma que se espera.
src/store/reducer.ts
Esta nueva evaluación se encarga de filtrar el jugador que se recibe mediante la acción de forma immutable.
Añadimos finalmente un evento a la lista de jugadores haciendo target en el elemento del evento y haciendo que haga un dispatch de REMOVE_PLAYER con el jugador asignado al atributo data-player.
Como podemos ver, es una librería sencilla con la que podemos gestionar el estado de nuestra app. Hemos eliminado todo el misterio que hay por detrás paso a paso. Ahora solo quedaría desarrollar herramientas que nos permitan mas funcionalidades o menos verbosidad a la hora de trabajar con ella.
Y no te olvides de suscribirte a nuestro boletín mensual para recibir más ejemplos de desmitificando Redux.
Si te gustó este artículo sobre desmitificando Redux, te puede gustar:
Arquitectura de microservicios vs arquitectura monolítica
Scala generics I: clases genéricas y type bounds
Scala generics II: covarianza y contravarianza
Principio de responsabilidad única
Sobre Dioses y procrastinación
Arquitectura de microservicios
Simular respuestas del servidor con Nodejs
Mapa de los “main players”: ecosistema startup y tech en Barcelona
Ecosistema de salud digital en Barcelona
Author
-
Frontend developer with extensive experience in microfrontends & microservices, TDD, DDD projects. Experience leading teams and departments, being CTO in startups companies or currently the Frontend developer lead at Apiumhub. Communication skills, developed during his experience teaching Apium Academy courses
Ver todas las entradas
2 Comments
Julio Espinoza
Excelente articulo, he utilizado Redux en aplicaciones pequeñas, pero tengo una duda en cuanto a la implementación e ideología de este, que propone tener una única fuente de de la verdad en app pequeñas, administrar el estado se ve razonable, pero que pasa con aquellas aplicaciones corporativas que están compuestas en módulos (Ventas, Compras, Contabilidad etc), pienso que al manejar todo este estado en un solo store se hace algo complejo y afectará el desempeño de la aplicación. Será posible manejar store para cada uno de esos módulos y dividir responsabilidades, agradezco me aclares la duda.
saludos
Albert Parrón
Hola Julio.
La idea de centralizar el estado en un único punto no significa que se deba gestionar desde uno también. Este estado puede ser dividido en módulos fácilmente y ser gestionado cada uno de forma independiente. Incluso puedes adoptar diferentes estrategias para cada módulo dependiendo de las necesidades de tu ViewModel.
Si esta gestión se hace modular, no vas a tener problemas de complejidad, además de tener mas facilidades a la hora de normalizar de el estado de tu app.
Espero que te haya servido de ayuda mi respuesta!
Un saludo