En esta publicación veremos como utilizar redux para obtener datos de una API. Con estos datos obtenidos vamos a llenar una lista de criptomonedas y agregar un buscador.
Para comprender el contenido debes de saber lo que es redux y sus principios. Si quieres más información fundamental sobre como usar redux, puedes revisar estas publicaciones:
Primera iteración, datos en duro en los componentes
La idea de empezar con los datos en duro es conocer el formato de lo que responde la API y como esos datos los queremos encajar en los componentes visuales.
¿Cómo son los datos de la respuesta del API?
Primero revisemos la forma de los datos que regresa la API. Estos datos son tomados de aquí. Más adelante simularemos la petición al API para facilitar el desarrollo.
Vamos a crear la interfaz pensando en los datos mostrados y componentes, de momento se me ocurre una tabla donde estarán listados los detalles del mercado y un campo de búsqueda.
Componente CryptoMarkets
Entonces agregamos un componente contenedor /src/CrytoMarkets/CryptoMarkets.js. En este componente contenedor vivirán los dos componentes antes mencionados, uno para hacer la búsqueda y otro para la tabla donde se listaran los mercados y el resultado de la búsqueda.
Y el componente Table en /src/CryptoMarkets/Table.js
import styles from"./Table.module.scss";exportdefaultfunctionTable(){return (<div><divclassName={styles.row}><p>Mercado</p><p>Moneda</p><p>Último precio</p><p>Volumen</p><p>Precio más alto</p><p>Precio más bajo</p><p>Variación 24hrs</p><p>Cambio 24hrs</p></div><divclassName={styles.row}><p>btc/mxn</p><p>btc</p><p>1007500.03</p><p>85.76293860</p><p>1031000.00</p><p>1003978.08</p><p>1015844.01</p><p>-1808.15</p></div><div><p>eth/btc</p><p>eth</p><p>0.08</p><p>52.02866824</p><p>0.08</p><p>0.08</p><p>0.08</p><p>-0.00040000</p></div></div> );}
Y para que se vea como una tabla le agregamos los siguientes estilos en /src/CrytoMarkets/Table.module.scss. Los estilos no es tema de esta publicación así que no le tomes mucha importancia por el momento, en caso de que se te compliquen.
Lo único es que si no estás haciendo el buscador en codesandbox y te sale algún problema con los estilos en sass, instala la librería de la siguiente forma.
En la tabla podemos notar que los divs que contiene los datos y el div del encabezado son muy similares, dentro se encuentran varios párrafos conteniendo cada pedazo de información. Así que crearemos un componente que hago eso, lo que contiene cada fila de la tabla.
Dentro del componente Table agregamos dos arreglos, uno para el header de la tabla y otro para una fila de la tabla. Como mencionamos antes, el header y las filas son muy similares, así que reutilizamos el mismo componente TableRow para generarlos.
Como te puedes dar cuenta el componente TableRow recibe items y className como propiedades, para que el header de la table no tenga el efecto del estilo :hover agregamos la clase .header.
Generando filas dinámicamente y dummies separados.
Usamos el nombre del mercado como el id porque es un dato que no se puede repetir. En este punto debemos tener algo así:
Segunda Iteración, agregando Redux
Para usar redux, necesitamos instalar la librería redux y para facilitar su uso en React instalamos también react-redux. Desde condesandbox puedes agregar estas dependencias en la parte inferior izquierda del editor donde dice “Dependencies“.
En tu computadora local corre el siguiente comando.
yarn add redux react-redux
En este punto ya sabemos la forma en que necesitamos los datos, la tabla necesita unos headers y una lista de filas con valores (rows).
Además, necesitamos crear un store que sea accesible desde cualquier parte de nuestra aplicación. El principal insumo que necesita el store es un reducer. Aún no obtendremos los datos de los mercados de la API, utilizaremos de momento los datos del archivo /src/CryptoMarkets/dummies.js
Importamos la función createStore que recibe una función reducer y opcionalmente un estado inicial. La función reducer de momento solo regresará el estado inicial tal cual, como sabemos, en redux las funciones reducers reciben un estado y una acción. Como segundo parámetro opcional le pasamos los datos dummies.
La función configureStore la utilizaremos a continuación.
Hacer al store accesible
Para que cualquier componente pueda acceder al store es necesario crear un contexto global, esto normalmente se realiza con React.createContext() y usando su componente Context.Provider. Por suerte la librería react-redux ya cuenta con este funcionamiento y podemos utilizar su propio componente Provider.
Si tienes curiosidad como usar el Context de React, puedes revisar esta publicación:
Al componente Provider debemos pasarle una propiedad store. Esa propiedad es el store generado por la función configureStore que creamos en la sección anterior.
Acceder al store desde el componente contenedor CryptoMarkets
Ahora que ya tenemos el store listo para usarse, vamos a obtener el estado para mostrar el listado de markets en el componente Table.
Usamos lo que en redux llaman selectors, los cuales son funciones que acceden a una parte especifica del estado para facilitar su acceso. Se usa el “custom hook” useSelector de react-redux para invocar las funciones selectoras.
Componente Table recibe headers y rows como propiedades
Finalmente, el componente Table debe poder recibir las propiedades headers y rows en lugar de importarlos del dummies.js
El resultado deberá ser el mismo de la primera iteración, pero con los datos obtenidos del store
Implementar la busqueda de mercados
La búsqueda que vamos a implementar será por el nombre del mercado. Así que basándonos en los datos del archivo /src/CrytoMarkets/dummies.js, vamos a modificar nuestra función reducer para agregar la acción de filtrado.
Creamos el archivo /src/CryptyoMarkets/cryptoMarketsState.js donde ahora vivirá nuestro reducer, separándolo del archivo /configureStore.js.
// state = { headers [], all = [], filtered = []}exportdefaultfunctioncryptoMarkets(state,action){switch (action.type) {case"FILTER_MARKETS":return{...state, filtered:state.all.filter((market)=>market.id.includes(action.payload) )};default:returnstate;}}
El comentario muestra la estructura del estado. La estructura sé cambio porque necesitamos guardar la lista original de mercados para ser utilizado en cada búsqueda, de lo contrario filtraríamos mercados sobre los últimos filtrados y así sucesivamente eliminando todos los mercados.
Una acción es un objeto literal que contiene un tipo y demás datos necesarios para cambiar el estado del store, estos datos los vamos a guardar en una propiedad llamada payload.
La acción 'FILTERED_MARKETS', filtra todos los markets que en su propiedad id incluyan el texto contenido en payload.
Disparar acción de filtrado desde el componente CryptoMarkets
Aunque ya creamos la acción, debemos dispararla cuando el usuario escribe en el campo de búsqueda, primero vamos a modificar el componente SearchField. Ahora además de recibir el label como propiedad, también la función que se ejecuta cada vez que el valor de búsqueda cambia.
Se usa el “custom hook” useDispatch de react-redux para disparar acciones desde cualquier componente funcional. Este hook regresa una función dispatch que se utiliza para disparar cualquier acción.
Al componente SearchField se le pasa la propiedad onSearchChange donde recibe la función que invoca la acción cuando el valor del campo de búsqueda cambia.
El resultado de esta iteración debe ser algo como lo siguiente:
Refactor, action creator para disparar búsqueda
Los action creators no son más que simples funciones que regresan una acción, y una acción es simplemente un objeto literal con un tipo y datos necesarios para la acción. En esta ocasión vamos a crear el action creator para la acción FILTER_MARKETS. En el archivo /src/CryptoMarkets/cryptoMarketsState.js agregamos al final lo siguiente
En el código anterior se importa el json en una variable llamada markets y se crea una promesa con un temporizador de un segundo, después del segundo la promesa se resuelve retornando los datos de los mercados (contenidos en la variable markets).
Usar redux-thunk para obtener los markets
¿Qué es un thunk?
Su definición en el ámbito de programación en general:
Los thunks se utilizan principalmente para retrasar un cálculo hasta que se necesite su resultado.
https://en.wikipedia.org/wiki/Thunk
En react se utiliza para escribir funciones en redux con lógica avanzada relacionadas con el estado de la aplicación. Donde se puede trabajar con operaciones tanto asíncronas como síncronas. Estas funciones thunk tienen acceso a las funciones dispatch() y getState() de redux, por esta razón un thunk internamente puede disparar cualquier numera de acciones y obtener los datos actuales del estado por si son necesarios en su lógica de programación.
Crear un Thunk action creator
En realidad un thunk se usa como se usan los action creators, por ese se les llama Thunk action creator.
En el codigo de arriba, el action creator regresa una función asíncrona que recibe solo la función dispatch (no vamos a necesitar la función getState()). La función comienza pidiendo los markets y una vez obtenidos, dispara la acción RECEIVE_MARKETS.
Actualizar reducer para entender la acción RECEIVE_MARKETS
En el archivo /CryptoMarkets/cryptoMarketsState.js agregamos él case para la acción RECEIVE_MARKETS. El código de este archivo debe quedar como el de abajo.
Para que el thunk se ejecute correctamente es necesario indicarle al store como tratar este tipo de funciones, para eso necesitamos agregar el middleware de react-thunk. Un middleware extiende o mejora la funcionalidad en un punto medio del software o comunica diferentes puntos de la aplicación, y también puede comunicar aplicaciones diferentes. De hecho en redux también se les llama enhancers.
Primero importamos la función applyMiddleware, luego importamos el middleware llamado thunk y finalmente aplicamos el middleware en el terecer parametro de createStore.
También cambiamos el initialState, los header los dejamos en el archivo dummies.js. Las propiedades all y filtered las cambiamos a array vacíos.
Invocando el thunk desde el componente CryptoMarkets
Por último, necesitamos invocar nuestro thunk para obtener los markets de nuestro servicio simulado. El archivo /CrytoMarkets/CryptoMarkets.js donde vive el componente padre CryptoMarkets debe quedar como el de abajo.
En este punto nuestra aplicación nos debe mostrar un error porque el formato de las propiedades de los componentes no son iguales a la respuesta del api, pero podemos agregar un console.log(action) en el reducer para comprobar que el api simulado está respondiendo con los datos.
En la imagen de arriba vemos en la consola que recibimos los datos del API simulado. En la siguiente sección eliminaremos este error mapeando correctamente los datos al formato de como los necesita el componente.
Mapeando datos a UI
Para que el error desaparezca, necesitamos mapear los datos obtenidos del servicio a datos que entienda los componentes, para esto creamos el archivo /mappers.js y vamos a crear las funciones de mapeo:
Ahora la función de mapeo es recibida por nuestro thunk, de esta manera, podemos utilizar diferentes mapeos para diferentes tipos de componentes. Imagínate que tenemos otra pantalla donde necesitamos mostrar la misma información en una lista de cards, ¿Verdad que el mapeo es diferente? ´Todo lo demás se deja intacto y solo cambiamos el mapeo.
Para esta pantalla donde tenemos una tabla, el resultado es el siguiente:
Estilos en campo de búsqueda
Un toque final, vamos a mejorar el campo de búsqueda creando estos estilos:
Todavía quedan más cosas por hacer y mejorar, al igual que hicimos en esta publicación incrementando en iteraciones pequeñas hasta conseguir que funcione el buscador, así tendremos más publicaciones para continuar mejorando e incrementando aún más las funcionalidades.
Estos dos React hooks se complementan a la hora de crear aplicaciones. Si tu aplicación no es tan grande y compleja, estos dos react hooks te ayudaran a manejar el estado global de una forma muy similar a como lo hace Redux.
Si no estás familiarizado con Redux, te recomiendo revisar los principios usados en Redux. Además la combinación de useContext y useReducer es un buen punto de partida para empezar a entender como funciona.
Primero aprenderemos a utilizar useContext, luego useReducer, y finalmente uniremos estos dos React Hooks para replicar el comportamiento básico de Redux y controlar el estado global de una pequeña aplicación.
Contexto
La idea del contexto es que ahí guardes cierta información que necesitas en varios componentes. El problema a resolver es que esos componentes se encuentran en diferentes niveles del árbol de nodos de tu aplicación. Por esta razón, una instancia de un contexto contiene dos propiedades componentes, un Proveedor y un Consumidor (Provider, Consumer). Como consecuencia el proveedor almacena y suministra los datos, mientras el consumidor pide acceso a ellos.
Agregando a useContext, existen tres formas de consumir el contexto actual de una aplicación
static contextType = UnContexto
<UnContexto.Consumer>
useContext(UnContexto)
Las tres maneras de obtener el contexto depende de que exista su correspondiente componente proveedor padre <UnContexto.Provider>. De las tres, la forma más simple es useContext(UnContexto), punto a favor de los React Hooks.
A continuación vamos a describir el ejemplo para la creación del contexto, más tres ejemplos de los modos de consumirlo.
createContext(defaultContext)
Primero necesitamos crear un contexto para poder consumirlo, la forma de crear el contexto no cambia para los consumidores.
Creamos un nuevo proyecto en codesandbox.io y creamos un archivo llamado UserNameContext.js con el siguiente contenido. En el ejemplo queremos obtener del nombre de un usuario en nuestra aplicación, que se usa en distintos componentes.
Ahora en nuestro archivo App.js importamos el contexto.
import UserNameContext from'./UserNameContext';
Necesitamos suministrar a la aplicación con los datos de nuestro contexto, para eso ocupamos al componente <UserNameContext.Provider> y actualizamos el valor del contexto cuando el usuario escribe en el campo de texto.
El componente <Header> es padre del componente <UserInfo>, de esta manera se ejemplifica el consumo del contexto en un tercer nivel de componentes.
El componente de <UserInfo /> contendrá al consumidor del contexto. Así que este es el único componente que cambiara su implementación para ejemplificar los tres modos de consumo del contexto. En las siguientes tres secciones se muestra cada forma.
static contextType = UnContexto
Para usar esta forma de consumir el contexto, debemos crear un componente de clase. La importación del contexto debe aplicarse también para los otros dos formas. Es algo que no cambia en las tres implementaciones.
Simplemente se agrega una propiedad estática contextType a la clase, asignándole como valor el objeto contexto generado por createContext(initialContext) anteriormente.
Luego podemos usar this.context en cualquiera de los métodos del ciclo de vida de un componente en React, para hacer referencia al valor actual del contexto y utilizarlo. Tal cual como se muestra en el ejemplo de arriba, donde utilizamos el método render().
<UnContexto.Consumer>
Esta manera de consumir el valor del contexto, pude usarse en un componente de clase o en un componente funcional, necesitamos importar el objeto del contexto y utilizar el componente Consumer que está definido como propiedad del objeto contexto.
Al componente Consumer mencionado en el párrafo anterior le pasamos como children una función, la cual puede recibir como primer parámetro el valor actual del contexto. Por último en este ejemplo mostramos el valor del contexto en una etiqueta <p>.
Uno de los objetivos de esta publicación es mostrar como usar el React Hook useContext(UnContexto), este hook es el equivalente a la propiedad static contextTypes = UnContexto en un componente de clase o el uso del componente <UnContexto.Consumer>, es decir, también puede obtener el valor actual del contexto y subscribirse a cambios del mismo.
Igual que las demás formas, importamos el objeto contexto, luego dentro del componente funcional, invocamos a useContext pasándole como parámetro el propio contexto. Así obtenemos el valor actual del contexto y lo guardamos en la constante value. Si el contexto cambia, esto provocara que el componente <UserInfo /> se vuelva a renderizar con el nuevo valor del contexto.
Compara la complejidad de obtener el contexto con useContext(UserNameContext), static contextType = UserNameContext y <UserNameContext.Consumer>.
Este React Hook es bastante interesante como alternativa a useState, se recomienda usarlo cuando:
Necesitas controlar el estado en algún objeto complejo, lo más común es en un objeto literal o Arreglo
Realizar lógica compleja relacionada con el estado previo para calcular el próximo estado.
useReducer, como su nombre lo indica utiliza una función reducer, esta función es la que se encarga de manejar la complejidad de los cambios en el estado de la misma forma que los reducers en Redux.
reducerFn, es la función reducer encargada de la lógica de actualizar el estado, es una función en la forma (state, action) => { }.
initialState, es el estado inicial.
initLazyFn, este parámetro es opcional, se usa para calcular el estado inicial final basado en el initialState, se invoca initLazyFn(initialState) para obtener el estado final. Útil para separar la lógica del cálculo del estado inicial de la función reducerFn.
Ejemplo
Creamos un nuevo proyecto en codesandbox.io, y en el archivo App.js ponemos lo siguiente:
El ejemplo es un formulario donde se guarda el nombre, apellido y edad de un usuario conforme escribe en sus respectivos inputs. Luego hasta el final tiene un botón para guardar al usuario en locaStorage al enviar el formulario.
Se tiene en archivos diferentes ainitialState,initLazyFn (parseInitialState) y reduceFn(reduceUser).
initialState e initiLazyFn
initialState.js, como ya mencionamos es el estado inicial.
exportdefault{name:"",age:0,lastName:""};
parseInitialState.js (initLazyFn), dado que al enviar el formulario se guarda en el localStorage, se debe parsear los datos del localStorage si es que existen.
reduceUser.js, la mayoría de las acciones realmente no son tan complejas, pero tampoco tan simples para usarlo en un useState(). El usuario guardado se puede almacenar en un arreglo de usuarios y que sea parte del estado, sin embargo no queremos complicar más las cosas, además necesitaríamos crear un componente que liste los usuarios.
Ahora bien, ya que tenemos todo el código de los archivos separados, podemos revisar el archivo App.js. En el componente App, en lugar de usar useState, se invoca a useReducer.
Se utiliza el evento onChange para disparar acciones con dispatch() y el evento onSubmit en el formulario para guardar en localStorage (“saveUser”). Aquí abajo el ejemplo completo.
Preparando codigo para combinar useContext y useReducer
Vamos a mejorar el ejemplo anterior, siguiendo las pautas que nos indica Redux, agregaremos todo lo relacionado con el estado en una carpeta llamada store.
Primero definimos el nombre de funciones como constantes en el archivo /store/actionTypes.js, para hacer referencia a ellas sin algún error de escribirlo incorrectamente.
Y finalmente vamos a actualizar las importaciones y las invocamos las funciones actions en App.js. Esto último para obtener el objeto action que necesita reduceUser(state, action) y tener un código más corto y entendible. Al menos en este caso nos evitamos la notación dispatch({ type: 'updateLastName', payload: e.target.value }). Ahora solo hacemos dispatch(updateLastName(e.target.value))
En esta sección vamos a utilizar useContext y useReducer para crear una pequeña aplicación al estilo Redux.
Primero vamos a dividir nuestra pequeña aplicación en varios componentes, agregaremos un componente <Header> el cual contendrá a otro llamado <UserInfo>. Luego otro componente <UserForm> donde usaremos el contexto para usar las acciones de nuestro reducer y el estado.
Header y UserInfo
Creamos una carpeta llamada components y creamos tres archivos, Header.js, UserInfo.js y UserForm.js.
En el componente <UserInfo>, a diferencia de la primera versión que hicimos al inicio de esta publicación, el valor del contexto es un arreglo conteniendo state y dispatch. Solo que en <UserInfo> solo nos interesa state.
Antes de crear el componente <UserForm>, vamos a crear el contexto que se utilizara en <UserInfo> y en <UserForm>.
Crear contexto
Creamos el archivo /store/UserContext.js dentro de la carpeta store
Como vemos agregamos el componente <Provider> del objeto UserContext y le asignamos como valor un arreglo conteniendo state y el dispatch. Si queremos podemos pasar solo una variable, así podemos decir que el valor del contexto es el store, en realidad esto es igual a como lo hace Redux, solo que redux trae su propio Provider.
Ahora si podemos crear el componente <UserForm> que utilizara el contexto y disparará las acciones a nuestra función reduceUser, es decir, utilizara el store de la aplicación.
Archivo /components/UserForm.js. Simplemente copiamos el contenido JSX del formulario que estaba App.js, importamos los action creators y el UserContext para utilizar useContext(UserContext) y obtener el store.
Listo podemos tener nuestra aplicación con un store igual al de Redux sin importar esta librería, esta funcionalidad ya viene construida dentro de React.
Por si no sabias como usar el contexto, en esta publicación aprendimos como crearlo y suministrar su valor a cualquier elemento hijo del árbol de nodos. Después para consumir el valor utilizamos las tres formas de hacerlo:
static contextType = UnContexto
<UnContexto.Consumer>
useContext(UnContexto)
Al final utilizamos el React Hook useContext y comparamos las complejidades, por consiguiente se puede notar que el useContext es la forma más simple de obtener el valor del contexto.
Aprendimos a utilizar useReducer() y como su funcionalidad es casi idéntica a los reducers de Redux. Luego mejoramos el código para separar la función reducer, los actions y los action types tal cual se haría en Redux separando responsabilidades.
Finalmente usamos createContext, useContext y useReducer para tener acceso al store de la misma forma que lo hacemos con Redux. Concluyendo que sin la necesidad de una librería de terceros, como lo es Redux, React ya contiene este tipo de funcionalidad gracias a los React Hooks useContext y useReducer. Estos hooks son ideales para aplicaciones pequeñas que no necesiten de las funcionalidades avanzadas de Redux.
Redux es un contenedor predecible del estado de aplicaciones JavaScript.
Te ayuda a escribir aplicaciones que se comportan de manera consistente, corren en distintos ambientes (cliente, servidor y nativo), y son fáciles de probar. Además de eso, provee una gran experiencia de desarrollo, gracias a edición en vivo combinado con un depurador sobre una línea de tiempo.
redux.org
Te permite controlar el flujo de datos de una aplicación Javascript, este flujo de los datos funciona en una sola dirección. Por esta única dirección es mucho más fácil controlar las mutaciones y operaciones asíncronas.
Redux es parecido a flux, de hecho está basado en flux
En el estado de una aplicación, principalmente en aplicaciones SPA, esto es muy importante, porque las aplicaciones web modernas tienen gran complejidad de operaciones asíncronas. Además de controlar el estado entre los componentes.
¿Qué problemas puedo mitigar con Redux?
En el ejemplo de aplicaciones web, estas necesitan controlar peticiones a servidores, obtener datos y controlar el estado en el cliente. Aun cuando estos datos no han sido guardados en el servidor.
Estas aplicaciones modernas requieren de un control complejo y por eso nuevos patrones de arquitectura como Redux y flux nacen para hacer el desarrollo más productivo.
Las mutaciones de objetos son difíciles de manejar y más a escalas medianas y grandes. Se comienza a perder el control de los datos, este flujo de datos generan comportamiento no deseados. Mala información desplegada al usuario y código que es muy difícil de mantener.
Sin contar cosas más elevadas como tu salud y causa de estrés por el esfuerzo en arreglar estas inconsistencias, te hace perder tiempo para mantener la aplicación funcionando correctamente. Por si fuera poco afectas a tus usuarios porque ellos obtienen información incorrecta y pierden su tiempo al utilizar esa información.
¿Qué decir de las operaciones asíncronas? Bueno, prácticamente pasa lo mismo o aún peor. Porque en una operación asíncrona no sabes cuando obtendrás el resultado y además normalmente vienen acompañadas con el deseo de hacer modificaciones al estado de la aplicación.
Un ejemplo común es el control de los datos de tu aplicación con cualquier frawework front-end de componentes visuales.
¿De qué está hecho Redux? Tres elementos
Store. El store es un objeto donde guardas toda la información del estado, es como el modelo de una aplicación, con la excepción que no lo puedes modificar directamente, es necesario disparar una acción para modificar el estado.
Actions. Son el medio por el cual indicas que quieres realizar una modificación en el estado, es un mensaje o notificación liviana. Solo enviando la información necesaria para realizar el cambio.
Reducers. Son las funciones que realizan el cambio en el estado o store, lo que hacen internamente es crear un nuevo estado con la información actualizada, de tal manera que los cambios se reflejan inmediatamente en la aplicación. Los reducers son funciones puras, es decir, sin efectos colaterales, no mutan el estado, sino que crean uno con información nueva.
¿Qué principios debo seguir? Tres principios
Un único Store para representar el estado de toda la aplicación. Tener una sola fuente de datos para toda tu aplicación permite tener centralizada la información, evita problemas de comunicación entre componentes para desplegar los datos, fácil de depurar y menos tiempo agregando funcionalidad o detectando errores.
Estado de solo lectura. Esto permite tener el control de cambios y evitar un relajo entre los diferentes componentes de tu aplicación, ni los componentes, ni peticiones ajax pueden modificar directamente el Estado (state) de tu aplicación, esto quiere decir que si quieres actualizar tu estado, debes hacerlo a través de actions, de esta manera redux se encarga de realizar las actualizaciones de manera estricta y en el orden que le corresponden.
Los cambios solo se hacen con funciones puras. Al realizar los cambios con funciones puras, lo que realmente se hace es crear un nuevo objeto con la información actualizada, estas funciones puras son los reducers y no mutan el estado, al no mutar el estado, se evitan problemas de control, datos incorrectos, mal comportamiento y errores, también permite que la depuración sea más fácil. Puedes dividir los reducer en varios archivos diferentes y las pruebas unitarias son fáciles de implementar. Los reducers son funciones puras que toman el estado anterior y una acción, y devuelven un nuevo estado.
Otras arquitecturas como MVC (Modelo, Vista, Controlador), los cambios pueden existir en ambas direcciones, es decir, la vista puede cambiar el estado, el modelo lo podría modificar y también el controlador. Todos estos cambios necesitan estar sincronizados en varias partes de una aplicación para evitar inconsistencias, lamentablemente este problema de inconsistencia se vuelve muy difícil y tedioso de resolver.
Lo anterior no sucede con Redux.
¿Cómo funciona? Mi primer estado predecible con Redux
Primero veamos el flujo de una sola dirección de redux:
Ahora un ejemplo concreto para saber de que estamos hablando, el control de un contador:
Si quieres probar este ejemplo en tu máquina recuerda insertar o importar la librería Redux. En este ejemplo podemos ver el funcionamiento de Redux, cada vez que se da clic sobre los botones de incrementar y decrementar, el contador se incrementa y decrementa.
Si ahora nos vamos al código JS, podremos ver que solo tenemos una función llamada counter, esta función es un reducer y es una función pura, sin efectos colaterales.
Luego vemos que como parámetros recibe un state y un action, cuando creamos nuestro Store con Redux, estas funciones reducer son utilizadas para modificar el State.
Normalmente, el state, reducer y action son definidos en puntos diferentes, y pueden ser organizados en varios archivos y carpetas dependiendo de la necesidad de la aplicación, toda esta organización tiene la finalidad de tener un código limpio y separar funcionalidades. En este caso sencillo no es necesario, en un solo archivo tenemos todo.
State
Nuestro state es solo un valor numérico, el cual se va a incrementar o decrementar con los botones que están en la página, normalmente el state es un objeto literal o un array literal, pero en nuestro caso solo necesitamos un número. Su definición podría estar en otro archivo, sin embargo, para este ejemplo no es necesario, lo agregamos como valor por defecto del parámetro state.
functioncounter(state=0,action){...
Actions
Si seguimos revisando nuestro código, veremos unos cuantas condiciones que revisa el valor del parámetro action y dependiendo de la acción, se ejecuta alguna operación, en nuestro caso son las operaciones INCREMENTAR o DECREMENTAR.
Los actions no son más que objetos literales como los siguientes:
Los reducer revisan esos actions, en nuestro ejemplo:
if (action.type ==='INCREMENTAR') {returnstate+1;}if (action.type ==='DECREMENTAR') {returnstate-1;}
Con todo esto ya tenemos nuestro State y su estado inicial en caso de no estar definido, también nuestra primer reducer, counter, y nuestros primeros actions INCREMENTAR y DECREMENTAR.
Store
Es el momento de crear nuestro Store, utilizando la librería redux esto es muy fácil:
const store = Redux.createStore(counter);
Con la anterior línea, Redux crea un Store para controlar el estado de nuestra aplicación. Internamente, un Store es un Patrón observador que utiliza un Singleton para el State y expone los siguientes métodos principales:
store.getState()
store.subscribe(listener)
store.dispatch(action)
store.getState() te permite obtener el estado actual de tu aplicación.
store.subscribe(listener) ejecuta la función listener (u observador), cada vez que el store es actualizado.
store.dispatch(action) pide actualizar el estado, esta modificación no es directa, siempre se realiza a través de un action y se ejecuta con un reducer.
Reaccionar a cambios del state
Luego creamos una función render, para que se ejecute cada vez que el State de tu aplicación cambie.
Aquí te preguntarás, ¿Cómo es posible que esa línea imprima 0 en la pantalla?, pues internamente el store invoca un dispatch con un action vacío store.dispatch({}), esto invoca nuestra función reducer y al no encontrar ninguna acción, entonces regresa el estado inicial 0.
Luego nos subscribimos a store para escuchar u observar el State cada vez que se actualice y poder ejecutar la función render().
store.subscribe(render);
Esta línea permite que cuando demos clic en los botones de incrementar y decrementar, se imprima el nuevo valor en la página volviendo a renderizar su contenido.
Ejecutar dispatch a través de eventos del DOM
Y por último agregamos dos listeners correspondientes a los botones de incrementar y decrementar, cada botón realiza una invocación dispatch para modificar el estado.
Al fin tenemos el flujo de datos de nuestro sencillo ejemplo usando Redux, en general los componentes que forman a una aplicación que utiliza Redux son los siguientes:
Conclusiones
Podemos notar el flujo en una sola dirección desde la vista con los actions dispatch(action), luego los reducers counter(prevState, action) para modificar el store y este último manda la información a través de subscribe(render) y getState().
Redux como ya se mencionó en párrafos anteriores, nos proporciona:
El conocimiento en todo momento del estado de nuestra aplicación y en cualquier parte de la aplicación con mucha facilidad.
Fácil organización, con actions, reducer y store, haciendo cambios en una sola dirección, además puedes separar tus actions, reducer y el store en varios archivos.
Fácil de mantener, debido a su organización y su único store, mantener su funcionamiento y agregar nuevas funcionalidades es muy sencillo.
Tiene muy buena documentación, su comunidad es grande y tiene su propia herramienta para debuguear
Todos los puntos anteriores hacen que sea fácil de hacer pruebas unitarias.
Y lo más importante, te ahorra tiempo, dinero y esfuerzo xD.