Ir al contenido principal
Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Escribir middleware personalizado

Lo que aprenderás
  • Cuándo usar middleware personalizado
  • Patrones estándar para middleware
  • Cómo asegurar que tu middleware sea compatible con otros proyectos Redux

El middleware en Redux se puede utilizar principalmente para:

  • crear efectos secundarios para las acciones,

  • modificar o cancelar acciones, o para

  • modificar la entrada aceptada por dispatch.

La mayoría de los casos de uso pertenecen a la primera categoría: por ejemplo, Redux-Saga, redux-observable y el middleware de escucha de RTK crean efectos secundarios que reaccionan a las acciones. Estos ejemplos también muestran que es una necesidad muy común: poder reaccionar a una acción de forma distinta a un cambio de estado.

Modificar acciones puede usarse para, por ejemplo, enriquecer una acción con información del estado o de una entrada externa, o para limitar su frecuencia (throttle), retrasar (debounce) o filtrar (gate).

El ejemplo más evidente de modificar la entrada de dispatch es Redux Thunk, que transforma una función que devuelve una acción en una acción llamándola.

Cuándo usar middleware personalizado

La mayoría de las veces no necesitarás middleware personalizado. El caso de uso más probable para middleware son los efectos secundarios, y existen muchos paquetes que empaquetan estos efectos para Redux de forma elegante y que llevan suficiente tiempo en uso para evitar los problemas sutiles con los que te encontrarías si lo construyeras tú mismo. Un buen punto de partida es RTK Query para gestionar el estado del servidor y el middleware de escucha de RTK para otros efectos secundarios.

Aún podrías querer usar middleware personalizado en uno de estos dos casos:

  1. Si solo tienes un efecto secundario único y muy simple, puede que no merezca la pena añadir un framework adicional completo. Solo asegúrate de cambiar a un framework existente cuando tu aplicación crezca, en lugar de desarrollar tu propia solución personalizada.

  2. Si necesitas modificar o cancelar acciones.

Patrones estándar para middleware

Crear efectos secundarios para acciones

Este es el tipo de middleware más común. Así es como se implementa en el middleware de escucha de RTK:

const middleware: ListenerMiddleware<S, D, ExtraArgument> =
api => next => action => {
if (addListener.match(action)) {
return startListening(action.payload)
}

if (clearAllListeners.match(action)) {
clearListenerMiddleware()
return
}

if (removeListener.match(action)) {
return stopListening(action.payload)
}

// Need to get this state _before_ the reducer processes the action
let originalState: S | typeof INTERNAL_NIL_TOKEN = api.getState()

// `getOriginalState` can only be called synchronously.
// @see https://github.com/reduxjs/redux-toolkit/discussions/1648#discussioncomment-1932820
const getOriginalState = (): S => {
if (originalState === INTERNAL_NIL_TOKEN) {
throw new Error(
`${alm}: getOriginalState can only be called synchronously`
)
}

return originalState as S
}

let result: unknown

try {
// Actually forward the action to the reducer before we handle listeners
result = next(action)

if (listenerMap.size > 0) {
let currentState = api.getState()
// Work around ESBuild+TS transpilation issue
const listenerEntries = Array.from(listenerMap.values())
for (let entry of listenerEntries) {
let runListener = false

try {
runListener = entry.predicate(action, currentState, originalState)
} catch (predicateError) {
runListener = false

safelyNotifyError(onError, predicateError, {
raisedBy: 'predicate'
})
}

if (!runListener) {
continue
}

notifyListener(entry, action, api, getOriginalState)
}
}
} finally {
// Remove `originalState` store from this scope.
originalState = INTERNAL_NIL_TOKEN
}

return result
}

En la primera parte, escucha las acciones addListener, clearAllListeners y removeListener para cambiar qué listeners deben invocarse posteriormente.

En la segunda parte, el código principalmente calcula el estado después de pasar la acción por los otros middlewares y el reducer, y luego pasa tanto el estado original como el nuevo estado proveniente del reducer a los listeners.

Es común tener efectos secundarios después de despachar la acción, porque esto permite tener en cuenta tanto el estado original como el nuevo, y porque la interacción proveniente de los efectos secundarios no debería influir en la ejecución de la acción actual (de lo contrario, no sería un efecto secundario).

Modificar o cancelar acciones, o modificar la entrada aceptada por dispatch

Aunque estos patrones son menos comunes, la mayoría de ellos (excepto cancelar acciones) son utilizados por el middleware redux thunk:

const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
({ dispatch, getState }) =>
next =>
action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}

// Otherwise, pass the action down the middleware chain as usual
return next(action)
}

Normalmente, dispatch solo puede manejar acciones JSON. Este middleware añade la capacidad de manejar acciones en forma de funciones. También cambia el tipo de retorno de la propia función dispatch, haciendo que el valor devuelto por la función-acción sea el valor de retorno de dispatch.

Reglas para crear middleware compatible

En principio, el middleware es un patrón muy potente que puede manipular las acciones a su antojo. Sin embargo, otros middleware existentes podrían asumir ciertos comportamientos en el flujo circundante. Ser consciente de estas suposiciones facilitará que tu middleware funcione correctamente con soluciones comúnmente utilizadas.

Existen dos puntos de contacto entre nuestro middleware y los demás:

Llamar al siguiente middleware

Cuando invocas next, el middleware espera recibir algún tipo de acción. A menos que necesites modificarla explícitamente, simplemente pasa la acción que recibiste sin cambios.

Más sutilmente, algunos middleware asumen que se les invoca en el mismo ciclo de eventos que dispatch, por lo que tu middleware debe llamar a next de forma síncrona.

Devolver el valor de retorno de dispatch

A menos que necesites modificar explícitamente el valor de retorno de dispatch, simplemente devuelve lo que obtengas de next. Si necesitas modificarlo, tu middleware deberá ubicarse en una posición muy específica de la cadena. En este caso, deberás verificar manualmente la compatibilidad con otros middleware y decidir cómo pueden cooperar.

Esto conlleva una consecuencia delicada:

const middleware: Middleware = api => next => async action => {
const response = next(action)

// Do something after the action hits the reducer
const afterState = api.getState()
if (action.type === 'some/action') {
const data = await fetchData()
api.dispatch(dataFetchedAction(data))
}

return response
}

Aunque aparentemente no modificamos la respuesta, en realidad lo hicimos: debido al async-await, ahora devuelve una promesa. Esto romperá algunos middleware como el de RTK Query.

Entonces, ¿cómo podemos reescribir este middleware?

const middleware: Middleware = api => next => action => {
const response = next(action)

// Do something after the action hits the reducer
const afterState = api.getState()
if (action.type === 'some/action') {
void loadData(api)
}

return response
}

async function loadData(api) {
const data = await fetchData()
api.dispatch(dataFetchedAction(data))
}

Simplemente extrae la lógica asíncrona en una función separada. Así podrás usar async-await sin esperar la resolución de la promesa dentro del middleware. Usar void indica a otros desarrolladores que has decidido no esperar explícitamente la promesa sin afectar el comportamiento.

Pasos siguientes

Si aún no lo has hecho, consulta la sección de Middleware en Understanding Redux para comprender cómo funciona internamente el middleware.