Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Reutilización de la lógica del reducer
A medida que una aplicación crece, empezarán a surgir patrones comunes en la lógica de los reducers. Puedes descubrir que varias partes de tu lógica de reducer realizan el mismo tipo de trabajo para diferentes tipos de datos, y querrás reducir la duplicación reutilizando la misma lógica común para cada tipo de dato. O quizá desees tener múltiples "instancias" de cierto tipo de dato gestionadas en el store. Sin embargo, la estructura global de un store de Redux conlleva ciertas compensaciones: facilita el seguimiento del estado general de una aplicación, pero también puede dificultar el "dirigir" acciones que necesiten actualizar una parte específica del estado, especialmente si estás usando combineReducers.
Como ejemplo, supongamos que queremos rastrear múltiples contadores en nuestra aplicación, llamados A, B y C. Definimos nuestro reducer inicial counter y usamos combineReducers para configurar nuestro estado:
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
const rootReducer = combineReducers({
counterA: counter,
counterB: counter,
counterC: counter
})
Desafortunadamente, esta configuración tiene un problema. Debido a que combineReducers llamará a cada slice reducer con la misma acción, despachar {type : 'INCREMENT'} hará que se incrementen los valores de los tres contadores, no solo uno de ellos. Necesitamos alguna forma de encapsular la lógica del counter para garantizar que solo se actualice el contador que nos interesa.
Personalización del comportamiento con higher-order reducers
Como se define en División de la lógica del reducer, un higher-order reducer es una función que toma un reducer como argumento y/o devuelve un nuevo reducer como resultado. También puede verse como una "fábrica de reducers". combineReducers es un ejemplo de higher-order reducer. Podemos usar este patrón para crear versiones especializadas de nuestras propias funciones reducer, donde cada versión solo responda a acciones específicas.
Las dos formas más comunes de especializar un reducer son: generar nuevas constantes de acción con un prefijo o sufijo determinado, o adjuntar información adicional dentro del objeto de acción. Así es como podrían verse:
function createCounterWithNamedType(counterName = '') {
return function counter(state = 0, action) {
switch (action.type) {
case `INCREMENT_${counterName}`:
return state + 1
case `DECREMENT_${counterName}`:
return state - 1
default:
return state
}
}
}
function createCounterWithNameData(counterName = '') {
return function counter(state = 0, action) {
const { name } = action
if (name !== counterName) return state
switch (action.type) {
case `INCREMENT`:
return state + 1
case `DECREMENT`:
return state - 1
default:
return state
}
}
}
Ahora deberíamos poder usar cualquiera de estos métodos para generar nuestros reducers de contador especializados, y luego despachar acciones que afecten a la porción del estado que nos interesa:
const rootReducer = combineReducers({
counterA: createCounterWithNamedType('A'),
counterB: createCounterWithNamedType('B'),
counterC: createCounterWithNamedType('C')
})
store.dispatch({ type: 'INCREMENT_B' })
console.log(store.getState())
// {counterA : 0, counterB : 1, counterC : 0}
function incrementCounter(type = 'A') {
return {
type: `INCREMENT_${type}`
}
}
store.dispatch(incrementCounter('C'))
console.log(store.getState())
// {counterA : 0, counterB : 1, counterC : 1}
También podríamos variar un poco el enfoque y crear un higher-order reducer más genérico que acepte tanto una función reducer dada como un nombre o identificador:
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const { name } = action
const isInitializationCall = state === undefined
if (name !== reducerName && !isInitializationCall) return state
return reducerFunction(state, action)
}
}
const rootReducer = combineReducers({
counterA: createNamedWrapperReducer(counter, 'A'),
counterB: createNamedWrapperReducer(counter, 'B'),
counterC: createNamedWrapperReducer(counter, 'C')
})
Incluso podrías ir más allá y crear un higher-order reducer de filtrado genérico:
function createFilteredReducer(reducerFunction, reducerPredicate) {
return (state, action) => {
const isInitializationCall = state === undefined;
const shouldRunWrappedReducer = reducerPredicate(action) || isInitializationCall;
return shouldRunWrappedReducer ? reducerFunction(state, action) : state;
}
}
const rootReducer = combineReducers({
// check for suffixed strings
counterA : createFilteredReducer(counter, action => action.type.endsWith('_A')),
// check for extra data in the action
counterB : createFilteredReducer(counter, action => action.name === 'B'),
// respond to all 'INCREMENT' actions, but never 'DECREMENT'
counterC : createFilteredReducer(counter, action => action.type === 'INCREMENT')
};
Estos patrones básicos te permiten hacer cosas como tener múltiples instancias de un componente conectado inteligente en la UI, o reutilizar lógica común para capacidades genéricas como paginación u ordenación.
Además de generar reducers de esta manera, también podrías querer generar creadores de acciones usando el mismo enfoque, y podrías generarlos ambos simultáneamente con funciones auxiliares. Consulta las bibliotecas de utilidades para Action/Reducer Generators y Reducers.
Patrón de reducer para colecciones/ítems
Este patrón te permite tener múltiples estados y usar un reducer común para actualizar cada estado basándose en un parámetro adicional dentro del objeto de acción.
function counterReducer(state, action) {
switch(action.type) {
case "INCREMENT" : return state + 1;
case "DECREMENT" : return state - 1;
}
}
function countersArrayReducer(state, action) {
switch(action.type) {
case "INCREMENT":
case "DECREMENT":
return state.map( (counter, index) => {
if(index !== action.index) return counter;
return counterReducer(counter, action);
});
default:
return state;
}
}
function countersMapReducer(state, action) {
switch(action.type) {
case "INCREMENT":
case "DECREMENT":
return {
...state,
state[action.name] : counterReducer(state[action.name], action)
};
default:
return state;
}
}