Middlewares
Los Middlewares son una parte fundamental en el entorno de Redux, quizás en la práctica
solo tengamos que aprender a utilizar ciertos middlewares como redux logger por motivos de logging
o redux thunk para manejar peticiones asíncronas.
Pero sería correcto aprender un poco más de ellos para tener un mejor entendimiento de nuestro código, y tener en cuenta la creación de un middleware a la hora de plantear una solución.
¿Ques un middleware?
Un middleware no es más que forma de interceptar las acciones que se envían a los reducers, dando la potestad de poder alterar el flujo de ejecución.
Dentro del código, un middleware tiene la siguiente forma:
export const myMiddlware = (api) => (next) => (action) => {
/* do something */
};
Cuando yo vi esto por primera vez, admito que no entendí mucho, así que decidí investigar un poquito más :).
¿De donde vienen los middlewares?
Bien, redux tiene muchos beneficios y uno de ellos es que el estado es inmutable. Cada acción que de como resultado un cambio en el estado, generará un nuevo estado y se guardará.
Entonces, teniendo cuenta esto, sería genial poder registrar todos esos cambios, o solo imprimirlos por consola.
Podemos plantear las siguientes soluciones:
Wrappear la función dispatch
const useDispathcAndLog = () => {
const dispatch = useDispatch();
const store = useStore();
const dispatchAndLog = (action: Action) => {
console.log("dispatching", action);
dispatch(action);
console.log("next state->" + store.getState());
};
return dispatchAndLog;
};
Esto solucionaría el problema, pero vamos a tener que importar esta función todo
el tiempo. Es una solución poco eficiente y no es la mejor.
Monkey patching
Monkey patching es una técnica utilizada para modificar el comportamiento por defecto
de una función en tiempo de ejecución. Es una buena opción para funcionar como Polyfil.
Entonces podemos aplicar esta técnica para llamar al mismo método del store, sin tener
que importarlo
const monkeyPathchingDispatch = () => {
const next = store.dispatch;
store.dispatch = (action) => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
};
monkeyPathchingDispatch();
Bien, analicemos lo que está pasando aquí:
- Guardo el dispatch original en una variable llamada
next
- Sobreescribo la función original agregando la funcionalidad de
logging
y ejecuto la original, porque a la vista del usuario esto no tiene que tener cambio alguno. - Se ejecuta la función al inicio de la aplicación
Bien, hasta aquí esto funciona muy bien, pero nos enfrentamos a ciertos problemas con este enfoque.
Bien, hasta aquí esto funciona muy bien, pero nos enfrentamos a ciertos problemas con este enfoque.
Esta técnica es considerada peligrosa, porque, este no es un comportamiento por defecto
de la api de Redux.Tener uno o más middlewares puede causar redundancia de código.
Para solucionar estos inconvenientes, se puede optar por hacer una manera genérica
de aplicar varios middleware
const loggerMiddleware = (store) => {
const next = store.dispatch;
return (action) => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
};
const createErrorAction = (message: string) => {
return {
type: "ERROR",
message: message,
};
};
const errorMiddleware = (store) => {
const next = store.dispatch;
return (action) => {
try {
return next(action);
} catch (error: { message: string }) {
return next(createErrorAction(error?.message));
}
};
};
const createErrorAction = (message: string) => {
return {
type: "ERROR",
message: message,
};
};
const errorMiddleware = (store) => {
const next = store.dispatch;
return (action) => {
try {
return next(action);
} catch (error: { message: string }) {
return next(createErrorAction(error?.message));
}
};
};
function applyMiddlewareByMonkeypatching(store, middlewares) {
middlewares = middlewares.slice();
middlewares.reverse();
middlewares.forEach((middleware) => (store.dispatch = middleware(store)));
}
applyMiddlewareByMonkeypatching(store, [logger, errorMiddleware]);
Hasta este momento se ha reducido la necesidad de aplicar la técnica por cada middleware. a solo una, pero todavía se sigue implementando.
Removiendo Monkey Patching
Todo lo anterior funciona, pero no de una manera muy eficiente, el primer problema
es que al hacer una llamada a store.dispatch
se llama a la función original.
Durante la ejecución del middleware, puede que este no sea el comportamiento que se desee.
En este caso el middleware errorMiddleware
al no acceder a dispatch wrapeado por
loggerMiddleware
, no podrá imprimir los detalles de la acción.
De ahí viene la idea de aceptar la función next
como parámetro, la cual es la
función previamente wrapeada por el middleware anterior.
const loggerMiddleware = (store) => {
return (next) => (action) => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
};
const errorMiddleware = (store) => {
return (next) => (action) => {
try {
return next(action);
} catch (error: { message: string }) {
return next(createErrorAction(error?.message));
}
};
};
Ahora next no es obtenido del store ,en lugar de eso, se acepta como parámetro
Ahora solo quedaría la aplicación de estos middleware funcionaría así.
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice();
middlewares.reverse();
let dispatch = store.dispatch;
middlewares.forEach((middleware) => (dispatch = middleware(store)(dispatch)));
return Object.assign({}, store, { dispatch });
}
Esta no es una implementación real de redux.
applyMiddleware. La función original hace ciertas mejoras.
- El store solo expone
dispatch
ygetState
- Se asegura de que llama a
Store.dispatch
, su acción recorrerá toda la cadena de middlewares. - Se asegura de que solo llame a
applyMiddleware
una vez.
El procedimiento de este artículo fue de la documentación de Redux.
Conclusión
Los middleware, sirven poder hacer algo con la acción antes de que llegué
al reducto final. La primera función del middleware es una función reducida del store
, la segunda función es la función dispatch
previamente empaquetada y la última es la función
que será llamada por el usuario.
Happy Coding😁
Top comments (0)