Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Más allá de combineReducers
La utilidad combineReducers incluida en Redux es muy útil, pero está deliberadamente limitada para manejar un único caso de uso común: actualizar un árbol de estado que es un objeto plano de Javascript, delegando el trabajo de actualizar cada porción del estado a un reductor específico. No maneja otros casos de uso, como un árbol de estado compuesto por Maps de Immutable.js, intentar pasar otras partes del árbol de estado como argumento adicional a un reductor de porción, o realizar un "ordenamiento" de las llamadas a los reductores de porción. Tampoco le importa cómo un reductor de porción específico realiza su trabajo.
La pregunta habitual entonces es: "¿Cómo puedo usar combineReducers para manejar estos otros casos de uso?". La respuesta es simple: "no lo hagas, probablemente necesitas usar otra cosa". Una vez que superas el caso de uso principal de combineReducers, es momento de usar lógica de reductores más "personalizada", ya sea lógica específica para un caso único o una función reutilizable que pueda compartirse ampliamente. Aquí hay algunas sugerencias para manejar un par de estos casos típicos, pero siéntete libre de crear tus propios enfoques.
Compartiendo datos entre reductores de porción
De manera similar, si sliceReducerA necesita algunos datos de la porción de estado de sliceReducerB para manejar una acción particular, o si sliceReducerB necesita todo el estado como argumento, combineReducers no maneja eso por sí mismo. Esto podría resolverse escribiendo una función personalizada que sepa pasar los datos necesarios como argumento adicional en esos casos específicos, como:
function combinedReducer(state, action) {
switch (action.type) {
case 'A_TYPICAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
b: sliceReducerB(state.b, action)
}
}
case 'SOME_SPECIAL_ACTION': {
return {
// specifically pass state.b as an additional argument
a: sliceReducerA(state.a, action, state.b),
b: sliceReducerB(state.b, action)
}
}
case 'ANOTHER_SPECIAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
// specifically pass the entire state as an additional argument
b: sliceReducerB(state.b, action, state)
}
}
default:
return state
}
}
Otra alternativa al problema de "actualizaciones de porciones compartidas" sería simplemente poner más datos en la acción. Esto se logra fácilmente usando funciones thunk o un enfoque similar, como en este ejemplo:
function someSpecialActionCreator() {
return (dispatch, getState) => {
const state = getState()
const dataFromB = selectImportantDataFromB(state)
dispatch({
type: 'SOME_SPECIAL_ACTION',
payload: {
dataFromB
}
})
}
}
Dado que los datos de la porción B ya están en la acción, el reductor padre no tiene que hacer nada especial para que esos datos estén disponibles para sliceReducerA.
Un tercer enfoque sería usar el reductor generado por combineReducers para manejar los casos "simples" donde cada reductor de porción puede actualizarse independientemente, pero también usar otro reductor para manejar los casos "especiales" donde los datos deben compartirse entre porciones. Luego, una función envolvente podría llamar a ambos reductores sucesivamente para generar el resultado final:
const combinedReducer = combineReducers({
a: sliceReducerA,
b: sliceReducerB
})
function crossSliceReducer(state, action) {
switch (action.type) {
case 'SOME_SPECIAL_ACTION': {
return {
// specifically pass state.b as an additional argument
a: handleSpecialCaseForA(state.a, action, state.b),
b: sliceReducerB(state.b, action)
}
}
default:
return state
}
}
function rootReducer(state, action) {
const intermediateState = combinedReducer(state, action)
const finalState = crossSliceReducer(intermediateState, action)
return finalState
}
Resulta que existe una utilidad útil llamada reduce-reducers que puede facilitar ese proceso. Simplemente toma múltiples reductores y ejecuta reduce() sobre ellos, pasando los valores de estado intermedios al siguiente reductor en la secuencia:
// Same as the "manual" rootReducer above
const rootReducer = reduceReducers(combinedReducers, crossSliceReducer)
Ten en cuenta que si usas reduceReducers, debes asegurarte de que el primer reductor en la lista pueda definir el estado inicial, ya que los reductores posteriores generalmente asumirán que todo el estado ya existe y no intentarán proporcionar valores predeterminados.
Sugerencias adicionales
Nuevamente, es importante entender que los reductores de Redux son simplemente funciones. Si bien combineReducers es útil, es solo una herramienta en la caja. Las funciones pueden contener lógica condicional además de sentencias switch, pueden componerse para envolverse mutuamente y pueden llamar a otras funciones. Quizás necesitas que uno de tus reductores de porción pueda restablecer su estado y solo responder a acciones específicas en general. Podrías hacer:
const undoableFilteredSliceA = compose(
undoReducer,
filterReducer('ACTION_1', 'ACTION_2'),
sliceReducerA
)
const rootReducer = combineReducers({
a: undoableFilteredSliceA,
b: normalSliceReducerB
})
Observa que combineReducers no sabe ni le importa que haya algo especial en la función reductora responsable de gestionar a. No necesitamos modificar combineReducers para que sepa específicamente cómo deshacer cosas; simplemente construimos las piezas que necesitábamos en una nueva función compuesta.
Además, aunque combineReducers es la única función de utilidad para reductores integrada en Redux, existe una amplia variedad de utilidades para reductores de terceros que se han publicado para su reutilización. El Catálogo de complementos de Redux enumera muchas de las utilidades de terceros disponibles. O bien, si ninguna de las utilidades publicadas resuelve tu caso de uso, siempre puedes escribir una función tú mismo que haga exactamente lo que necesitas.