DEV Community

Cover image for ¿Que es un middleware en redux?
leobar37
leobar37

Posted on • Edited on

¿Que es un middleware en redux?

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 */
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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));
    }
  };
};
Enter fullscreen mode Exit fullscreen mode

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 });
}
Enter fullscreen mode Exit fullscreen mode

Esta no es una implementación real de redux.
applyMiddleware. La función original hace ciertas mejoras.

  • El store solo expone dispatch y getState
  • 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)