Redux es una librería, que nos ayuda a manejar y actualizar estado de nuestra aplicación, he implementa también el patrón Flux que nos dice como debemos tratar ese estado y sus actualizaciones.
El estado es de solo lectura, no modificarlo directamente sino atraves de actions.
Los cambios deben realizarse através de funciones puras (reducers)
La diferencia entre State y Store es que State es un objeto de tipo clave-valor (aunque puede ser de otros tipos). El Store contiene al state y a otras propiedades como los disparadores, suscribers etc..
Funciones puras
Valor retornado cambia si la entrada cambia.
Misma entrada, misma salida.
Sin efectos colaterales
Reducers
Calcular el nuevo estado basado en los parámetros (state, action).
No modificar el estado directamente. (Inmutabilidad)
No tener lógica asíncrona.
Ciclo de vida de Redux
Diferencias entre Redux y Context API
Depuración
Redux tiene un depurador que nos permite llevar trazabilidad de como se ha ido comportando nuestro estado.
Context API al tener todo en el mismo provider y no tener una descripción clara de lo que cambia se más complicado de depurar.
Bundle size
Redux necesita de librerías o paquetes externos y requiere ser manualmente integrado para poder ser utilizado.
Context API ya viene por defecto integrado y no necesita librerías externas para ser utilizado.
Middlewares
Redux tiene una manera muy simple de extender funcionalidades o acciones con los Middlewares.
Curva de aprendizaje
Redux tiene una curva de aprendizaje mas robusta y al inicio puede ser complicado, se debe saber integrar, aprender un nuevo paradigma y como fluyen los datos con Redux en la aplicación.
Context API es fácil de aprender y mucho mas ligero.
Rendering
Redux evita render innecesarios.
Context API no evita estos render innecesarios, sin embargo, hay técnicas para hacerlo pero se deben hacer manualmente.
Ejemplo Básico
import*as Redux from'https://unpkg.com/redux@latest/dist/redux.browser.mjs'// Un reducer es una maquina de estados que recive el// estado y la accion a realizar y devuelve un nuevo estadofunctioncounterReducer(state, action) {if (typeof state ==='undefined') {return0; }switch (action.type) {case'INCREMENT':return state +1;case'DECREMENT':return state -1;default:return state; }}// Se crea el storevar store =Redux.createStore(counterReducer);functionrender() {console.log(store.getState().toString());}render();// El subscriber esta escuchando nuevos cambiso al statestore.subscribe(render);// Despachadores disparan las acciones y las mandan al reducerstore.dispatch({ type:'INCREMENT' });store.dispatch({ type:'DECREMENT' });
Boilerplate -> Los hooks ahorran mucho código aquí.
Separación de responsabilidades -> Connect hace un High Order Component, así que es mejor.
Testing -> Es un poco más fácil con Connect.
Middlewares
Un middleware es un paso intermedio entre 2 procesos, en el caso de redux los middlewares se ejecutan despues de cuando se dispara una acción y antes de que lleguen al reducer.
Ejemplo básico
// ./middlewares/index.js// Logexportconstlogger= (store:any) => { // store de la aplicación return (next: any) => { // next es una función que se llama cuando el middleware termina su trabajo y envía el action al reducer
return (action:any) => { // action es la información que se pasa al reducerconsole.log(action);next(action); // <-- hace que el action llegue al reducer } }};// Alterar o cambiar el estado original antes de que llegue al reducerexportconstfeaturing= (store:any) => {return (next:any) => {return (actionNew:any) => {debugger;constfeatured= [ { name:'Momea', url:'https://ab.c' },// <-- nuevo pokemon que quiero meter al state...actionNew.payload,// <-- desestructura todos los pokemons que están en el payload de 'action' ];constupdatedAction= {...actionNew, payload: [...featured, ] }next(updatedAction); } }};
Es un Middleware que extiende las capacidades del store (enhancer), nos permite realizar acciones asíncronas en nuestros actions creators que por defecto no podemos hacer
Thunk es un concepto de programación donde se utiliza una función intermedia para retrasar la evaluación o ejecución de una operación, como puede ser la petición de una data a una API y demás acciones
para este caso en particular separaremos responsabilidades, delegando la petición a la API a nuestro action creator y no a el componente como se venia trabajando
Instalación
npm install redux-thunk
Integración simplemente debemos importarlo y añadirlo a nuestra composición de middlewares src/index.js:
Una vez integrado procederemos a crear nuestro action creator que será una función la cual contendrá la petición a la API que veníamos trabajando, esa función hará la petición a la API, obtendrá su respuesta y devolverá otra función la cual recibe como parámetro el dispatch para poder disparar acciones, este dispatch recibirá como parámetro la acción setPokemons que a su vez recibirá como parámetro la respuesta de la petición a la API src/actions/index.js:
// src/actions/index.jsexport const getPokemonsWithDetails = (pokemons = [])=> async (dispatch)=> { // con el dispatch(next) es con lo que funciona redux thunk
constpokemonsDetailed=awaitPromise.all(pokemons.map(pokemon =>getPokemonDetail(pokemon)))dispatch(setPokemons(pokemonsDetailed))}
Separación de responsabilidades
Una vez creada el action creator lo importamos y podemos hacer dispatch con el pasándole como parámetro nuestro arreglo de objetos de pokemones src/App.js:
Potenciadores del store. Son funciones de orden superior que toma un store creator y devuelve una versión potenciada. Similar a los middlewares ya que te permite alterar la interfaz del store de manera combinable, por ejemplo los dev tools es un potenciador del store.
Compose
Es una herramienta de programación funcional que combina funciones de derecha a izquierda y lo usamos para poder tener mutiples potenciadores del store.
Redux Toolkit
Es una libreria que nos ayuda a simplificar el uso de redux
npm i @reduxjs/toolkit
creamos carpeta
// ./slice/dataSlice.jsimport { createSlice } from"@reduxjs/toolkit";constinitialState= { pokemons: [], loading:true,};exportconstdataSlice=createSlice({ name:"data", initialState, reducers: {setPokemons: (state, action) => {state.pokemons =action.payload;state.loading =false; },setFavorite: (state, action) => {constindexPkmn=state.pokemons.findIndex( (pkmn) =>pkmn.id ==action.payload.id );if (indexPkmn >=0) {state.pokemons[indexPkmn].isFavorite =!state.pokemons[indexPkmn].isFavorite; } }, },});exportconst {setFavorite,setPokemons}=dataSlice.actions//exportamos por default los reducer del objeto dataSlice para integrarlos en la//raiz que integra todos nuestros reducers exportdefaultdataSlice.reducer
ahora pasaremos a pasar nustro slice a la raiz de nuestros reducer
// ./slice/rootReducer.jsimport {combineReducers} from'redux';import { pokemonsReducer } from'./pokemons';import dataReducer from'../slices/dataSlice'; //como estamos utilicemmos la libreria de immutable, para // user reducer combinados, necesitamos instalar redux-immutableconstrootReducer=combineReducers({ data: dataReducer})exportdefault rootReducer;