Redux: Una preocupación en combineReducer, cuando una acción está relacionada con múltiples reductores

Creado en 21 ago. 2015  ·  52Comentarios  ·  Fuente: reduxjs/redux

Estoy trabajando en una aplicación de chat creada con React.js y Flux. Mientras leo los documentos de Redux, la función combineReducer me parece extraña. Por ejemplo:

Hay tres tiendas llamadas messageStore , unreadStore , threadStore . Y hay una acción llamada NEW_MESSAGE . Las tres tiendas se actualizarán con los mensajes nuevos. En Redux, las tiendas son reductores combinados con combineReducer como:

message = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
unread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
thread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state

combineReducer {message, unread, thread}

Me parece que en tales casos, dividir reductores no me facilita la vida. Sin embargo, una tienda gigante construida con inmutable-js quizás sea una mejor solución para esto. Me gusta:

initialState = Immutable.fromJS
  message: []
  unread: []
  thread: []

allStore = (store=initialState, action) ->
  switch action.type
    when NEW_MESSAGE
        initialState
        .updateIn ['message'], (messages) -> messages # updated
        .updateIn ['urnead'], (urneads) -> unreads # updated
        .updateIn ['thread'], (threads) -> threads # updated
question

Comentario más útil

para tal acción, tengo que mantenerlo en varios reductores (o archivos después de que mi aplicación crezca). En nuestra base de código actual, ya vimos 5 tiendas manejando la misma acción, es un poco difícil de mantener.

Si esto es bueno o malo depende de su aplicación. En mi experiencia, tener muchas tiendas (o reductores de Redux) que manejan la misma acción es realmente muy conveniente porque divide las responsabilidades y también permite que las personas trabajen en las ramas de funciones sin chocar con el resto del equipo. En mi experiencia, es más fácil mantener mutaciones no relacionadas por separado que una mutación gigante.

Pero tienes razón, hay casos en los que esto no funciona demasiado bien. Yo diría que a menudo son síntomas de un modelo de estado subóptimo. Por ejemplo,

en algunos casos, un reductor puede depender de los datos de otro (como mover un mensaje de no leído a mensaje, o incluso promover un mensaje como un nuevo hilo de mensaje a hilo)

es un síntoma de un problema. Si tiene que mover muchas cosas dentro de su estado, tal vez la forma del estado deba estar más normalizada. En lugar de { readMessages, unreadMessages } puede tener { messagesById, threadsById, messageIdsByThreadIds } . Mensaje leído? Bien, alterna state.messagesById[id].isRead . ¿Quieres leer los mensajes de un hilo? Coge state.threadsById[threadId].messageIds , luego messageIds.map(id => state.messagesById[id]) .

Cuando los datos están normalizados, no es necesario copiar elementos de una matriz a otra. La mayoría de las veces estaría volteando banderas o agregando / eliminando / asociando / disociando ID.

Todos 52 comentarios

¿Puede explicar por qué cree que combineReducers no es útil en este caso? De hecho, para un estado similar a una base de datos, a menudo desea un solo reductor (y luego otros reductores para el estado de la interfaz de usuario, por ejemplo, el elemento seleccionado, etc.).

Ver también:

https://github.com/rackt/redux/tree/master/examples/real-world/reducers
https://github.com/rackt/redux/blob/master/examples/async/reducers

por inspiración.

El primer ejemplo que señaló es similar a mi caso, requestType se maneja en ambos reductores.
https://github.com/rackt/redux/blob/master/examples/real-world/reducers/paginate.js#L28

La división de reductores facilita el mantenimiento cuando dos reductores están separados de los demás. Pero cuando se dedican a las mismas acciones, conduce a:

  • para tal acción, tengo que mantenerlo en varios reductores (o archivos después de que mi aplicación crezca). En nuestra base de código actual, ya vimos 5 tiendas manejando la misma acción, es un poco difícil de mantener.
  • en algunos casos, un reductor puede depender de los datos de otro (como mover un mensaje de unread a message , o incluso promover un mensaje como un nuevo hilo de message a thread ). Por lo tanto, es posible que deba obtener datos de otro reductor ahora.

No veo una buena solución para estos dos problemas. En realidad, no estoy totalmente seguro de estos dos problemas, son como compensaciones.

para tal acción, tengo que mantenerlo en varios reductores (o archivos después de que mi aplicación crezca). En nuestra base de código actual, ya vimos 5 tiendas manejando la misma acción, es un poco difícil de mantener.

Si esto es bueno o malo depende de su aplicación. En mi experiencia, tener muchas tiendas (o reductores de Redux) que manejan la misma acción es realmente muy conveniente porque divide las responsabilidades y también permite que las personas trabajen en las ramas de funciones sin chocar con el resto del equipo. En mi experiencia, es más fácil mantener mutaciones no relacionadas por separado que una mutación gigante.

Pero tienes razón, hay casos en los que esto no funciona demasiado bien. Yo diría que a menudo son síntomas de un modelo de estado subóptimo. Por ejemplo,

en algunos casos, un reductor puede depender de los datos de otro (como mover un mensaje de no leído a mensaje, o incluso promover un mensaje como un nuevo hilo de mensaje a hilo)

es un síntoma de un problema. Si tiene que mover muchas cosas dentro de su estado, tal vez la forma del estado deba estar más normalizada. En lugar de { readMessages, unreadMessages } puede tener { messagesById, threadsById, messageIdsByThreadIds } . Mensaje leído? Bien, alterna state.messagesById[id].isRead . ¿Quieres leer los mensajes de un hilo? Coge state.threadsById[threadId].messageIds , luego messageIds.map(id => state.messagesById[id]) .

Cuando los datos están normalizados, no es necesario copiar elementos de una matriz a otra. La mayoría de las veces estaría volteando banderas o agregando / eliminando / asociando / disociando ID.

Me gustaría intentar eso.

Acabo de escribir una página de demostración para probar redux. Creo que combineReducer trajo bastante complejidad. Y todos los ejemplos en el repositorio están usando combineReducer , ¿todavía existe la posibilidad de que pueda usar un solo objeto de datos inmutables como modelo, en lugar de un objeto de JavaScript que contenga mis datos?

@jiyinyiyong Realmente no estoy seguro de qué complejidad estás hablando, pero no combineReducers . Es solo un ayudante. Echa un vistazo a un ayudante similar que usa Immutable en su lugar . O escribe el tuyo, por supuesto.

Escribí mi propio combineReducers() helper principalmente para admitir Immutable.js, pero puede necesitar reductores adicionales que se tratan como reductores de raíz:
https://github.com/jokeyrhyme/wow-healer-bootcamp/blob/master/utils/combineReducers.js

El primer argumento coincide con la forma de su estado y pasa los sub-estados de nivel superior a los sub-reductores coincidentes que usted proporciona. Es más o menos lo mismo que la versión oficial.

Sin embargo, es opcional agregar cero o más argumentos adicionales, estos se tratan como otros reductores de raíz. Estos reductores pasan el estado completo. Esto permite que las acciones se envíen de formas que requieran más acceso que el patrón de sub-reductor recomendado.

Obviamente, no es una gran idea abusar de esto, pero he encontrado un caso extraño en el que es bueno tener múltiples sub-reductores así como múltiples reductores de raíz.

Quizás en lugar de agregar múltiples reductores raíz, un paso intermedio podría ser usar los argumentos adicionales para definir los sub-reductores que necesitan un acceso más amplio (aún menos que el acceso total). Por ejemplo:

function a (state, action) { /* only sees within a */ return state; }
function b (state, action) { /* only sees within b */ return state; }
function c (state, action) { /* only sees within c */ return state; }

function ab (state, action) { /* partial state containing a and b */ return state; }
function bc (state, action) { /* partial state containing b and c */ return state; }

let rootReducer = combineReducers(
  { a, b, c },
  [ 'a', 'b', ab ],
  [ 'b', 'c', bc ]
);

¿Vale la pena enviar un PR para reductores parciales y reductores de raíz adicionales? ¿Es esta una idea horrible o es algo que podría ser útil para otras personas?

Tengo más ganas de aprender cómo el halógeno y el olmo están lidiando con estos problemas. JavaScript está mezclado con varios diagramas de programación y nos lleva a muchas formas. Ahora estoy confundido cuál es el correcto de FRP y flujo de datos unidireccional.

He descubierto que esto también es un punto de confusión para mí. Solo publico mi caso de uso / solución aquí.

Tengo tres subductores que manejan una variedad de tres tipos de recursos diferentes (clientes, proyectos, trabajos). Cuando elimino un cliente ( REMOVE_CLIENT ), todos los proyectos y trabajos relacionados deben eliminarse. Los trabajos están relacionados con los clientes a través de un proyecto, por lo que el reductor de trabajos debe tener acceso a los proyectos para realizar la lógica de unión. El reductor de trabajos debe obtener una lista de todos los ID de proyectos que pertenecen al cliente que se está eliminando y luego eliminar cualquier trabajo coincidente de la tienda.

Solucioné este problema con el siguiente middleware:

export default store => next => action => {
  next({ ...action, getState: store.getState });
}

Para que cada acción tenga acceso al almacén raíz a través de action.getState() .

@ rhys-vdw ¡gracias por eso! Un middleware realmente útil :)

@ rhys-vdw Gracias por esto, parece una buena solución. ¿Cómo se manejan los casos extremos / la integridad referencial, por ejemplo, cuando una entidad es compartida por dos (o más) otras entidades o apuntando a un registro no existente durante la eliminación? Solo estoy ansioso por escuchar tus pensamientos sobre esto.
@gaearon ¿Hay alguna forma documentada en Redux de cómo resolver este tipo de problema de integridad referencial de las entidades normalizadas?

No hay una forma específica. Me imagino que se puede automatizar esto definiendo un esquema y generando reductores basados ​​en este esquema. Entonces "sabrían" cómo recolectar "basura" cuando se borren cosas. Sin embargo, todo esto es muy ondulado y requiere un esfuerzo de implementación :-). Personalmente, no creo que la eliminación ocurra con la suficiente frecuencia como para justificar una limpieza real. Por lo general, voltearía un campo status en el modelo y me aseguraría de no mostrar los elementos eliminados al seleccionarlos. O, por supuesto, puede actualizar al eliminar si no es una operación común.

@gaearon Saludos por la respuesta súper rápida. Sí… creo que uno tiene el mismo esfuerzo para tratar la integridad referencial cuando se usan referencias con bases de datos basadas en documentos como Mongo. Creo que vale la pena tener un estado puro sin entidades de basura flotando alrededor. No estoy seguro de si esas entradas fantasmas podrían hacerte tropezar, especialmente cuando tienes muchas operaciones CRUD en tu estado.

Sin embargo, también creo que sería genial si Redux tuviera soporte tanto para entidades normalizadas como integradas. Y uno podría elegir una estrategia que se ajuste a su propósito. Pero supongo que para las entidades integradas se debe usar nested reducers que es un anti-patrón según los documentos de Redux.

Me resultó difícil acceder al resto del estado de la tienda en un reductor. Hace que los combineReducers anidados sean realmente difíciles de usar en algunos casos. Sería bueno si hubiera métodos para acceder al resto del estado como .parent() o .root() o equivalente.

Acceder al estado de otros reductores en un reductor suele ser un anti-patrón.
Por lo general, se puede resolver mediante:

  • Quitar esa lógica del reductor y moverla a un selector;
  • Pasar información adicional a la acción;
  • Dejar que el código de vista realice dos acciones.

¡Gracias! Estoy de acuerdo con eso. Descubrí que se volvió más complejo después de intentar pasar el estado raíz a reductores :-)

Sí, el problema de pasar el estado raíz es que los reductores se acoplan a la forma del estado del otro, lo que complica cualquier refactorización o cambio en la estructura del estado.

Bien, veo claramente los beneficios de tener un estado normalizado y trato la parte redux de mi estado como un DB.

Todavía estoy pensando si el enfoque de (marcar entidades para su eliminación) o de @ rhys-vdw es mejor (resolver las referencias y eliminar las referencias relacionadas en su lugar) .

¿Ideas / experiencias del mundo real?

Si redux se parecía más al olmo y el estado no estaba normalizado, entonces el desacoplamiento de los reductores de la forma del estado tiene sentido. Sin embargo, dado que el estado está normalizado, parece que los reductores a menudo necesitan acceso a otras partes del estado de la aplicación.

Los reductores ya están acoplados transitivamente a la aplicación, ya que los reductores dependen de las acciones. No puede reutilizar trivialmente reductores en otra aplicación redux.

Trabajar en torno al problema impulsando el acceso del estado a acciones y utilizando middleware parece una forma mucho más desagradable de acoplamiento que permitir que los reductores accedan al estado raíz directamente. Ahora las acciones que a menudo no lo requieren también se acoplan a la forma del estado y hay más acciones que reductores y más lugares para cambiar.

Pasar el estado a la vista en acciones quizás aísle mejor los cambios de forma de estado, pero si se necesita un estado adicional a medida que se agregan características, se deben actualizar tantas acciones.

En mi humilde opinión, permitir el acceso de los reductores al estado raíz resultaría en un acoplamiento general menor que las soluciones alternativas actuales.

@kurtharriger : tenga en cuenta que, si bien el patrón común recomendado es estructurar el estado de Redux por dominio y usar combineReducers para administrar cada dominio por separado, esa es _sólo_ una recomendación. Es completamente posible escribir su propio reductor de nivel superior que gestione las cosas de manera diferente, incluido pasar todo el estado a funciones específicas de sub-reductor. En última instancia, solo tiene una función reductora, y la forma en que la descompone internamente depende completamente de usted. combineReducers es solo una utilidad útil para un caso de uso común.

La respuesta de Preguntas frecuentes de Redux sobre la división de la lógica empresarial también tiene una buena discusión sobre este tema.

Sí, otra opción sería usar un solo reductor en una función gigante y / o escribir su propia función combineReducer en lugar de usar la integrada.
Tener un método combineReducers incorporado que agregue un argumento de estado raíz adicional podría ser útil, pero los RP que han intentado agregar esta funcionalidad se han cerrado como un anti-patrón. Solo quería argumentar el punto de que las alternativas propuestas en mi humilde opinión dan como resultado un acoplamiento mucho más general y tal vez esto no debería considerarse un anti-patrón. Además, si se agregó el estado raíz como un argumento adicional, no es como si estuviera obligado a usarlo, por lo que el acoplamiento sigue siendo una opción.

Queremos que este acoplamiento sea explícito. Es explícito cuando lo haces manualmente y son literalmente N líneas de código donde N es la cantidad de reductores que tienes. Simplemente no use combineReducers y escriba ese reductor principal a mano.

Soy nuevo en redux / react, pero humildemente preguntaría esto: si los reductores compuestos que acceden al estado del otro es un anti-patrón porque agrega acoplamiento entre áreas dispares de la forma del estado, yo diría que este acoplamiento ya está ocurriendo en mapStateToProps ( ) de connect (), ya que esa función proporciona el estado raíz y los componentes se extraen de todas partes para obtener sus accesorios.

Si se van a encapsular subárboles de estado, los componentes deben tener acceso al estado a través de accesores que oculten esos subárboles, de modo que la forma del estado interna a esos alcances se pueda administrar sin refactorizar.

A medida que aprendo redux, estoy luchando con lo que imagino que será un problema a largo plazo con mi aplicación, que es un acoplamiento estrecho de la forma del estado en la aplicación, no por reductores sino por mapStateToProps, como una barra lateral de comentarios que debe conocer el nombre de usuario de autenticación (cada uno reducido por un reductor de comentarios frente a un reductor de autenticación, por ejemplo). Estoy experimentando con envolver todo el acceso de estado en métodos de acceso que hacen este tipo de encapsulación, incluso en mapStateToProps, pero siento que podría estar ladrando mal árbol.

@jwhiting : ese es uno de los varios propósitos de usar "funciones de selección" para extraer los datos que necesita del estado, particularmente en mapStateToProps . Puede ocultar el estado de su forma para que haya un número mínimo de lugares que realmente necesiten saber dónde está state.some.nested.field . Si aún no lo ha visto, consulte la biblioteca de utilidades Reselect y la sección Computing Derived Data de los documentos de Redux.

Si. Tampoco tiene que usar Reselect si su volumen de datos es pequeño pero aún usa selectores (escritos a mano). Vea el ejemplo shopping-cart para este enfoque.

@markerikson @gaearon eso tiene sentido y lo exploraré, gracias. El uso de selectores en mapStateToProps para cualquier cosa fuera de la preocupación principal de ese componente (o quizás para el acceso de todos los estados) protegería a los componentes de los cambios en la forma del estado extranjero. Sin embargo, me gustaría encontrar una manera de hacer que la encapsulación de estado se aplique de manera más estricta en mi proyecto, de modo que la disciplina del código no sea la única protección y las sugerencias bienvenidas allí.

Mantener todo el código en un solo reductor no es realmente una buena separación de
preocupaciones. Si su reductor es llamado por la raíz, puede llamarlo manualmente
y pasar explícitamente el estado raíz como creo que sugieres, pero si tu
la jerarquía del reductor padre contiene combineReducers tienes que ir a rasgarlo
afuera. Entonces, ¿por qué no hacerlo más compostable para que no tengas que rasgarlo?
más tarde o recurrir a thunks como la mayoría de la gente parece hacer?
El sábado 16 de abril de 2016 a las 8:27 p.m. Josh Whiting [email protected]
escribió:

@markerikson https://github.com/markerikson @gaearon
https://github.com/gaearon eso tiene sentido y lo exploraré,
gracias. Usando selectores en mapStateToProps para cualquier cosa fuera de eso
principal preocupación del componente (o quizás para el acceso de todos los estados) protegería
componentes de cambios en la forma del estado extranjero. Me gustaria encontrar un camino
Sin embargo, para hacer que la encapsulación de estado se aplique de manera más estricta en mi proyecto,
para que la disciplina del código no sea la única protección y bienvenida
sugerencias allí.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/reactjs/redux/issues/601#issuecomment -210940305

@kurtharriger : todos estamos de acuerdo en que mantener hasta el último bit de la lógica del reductor en una sola función es una mala idea, y que el trabajo debería delegarse a subfunciones de alguna manera. Sin embargo, todos van a tener diferentes ideas sobre cómo hacer eso y diferentes necesidades para su propia aplicación. Redux simplemente proporciona combineReducers como una utilidad incorporada para un caso de uso común. Si no desea utilizar esa utilidad tal como existe actualmente, no es necesario que lo haga; puede estructurar sus reductores a su manera, ya sea escribiendo todo a mano o escribiendo su propia variación de combineReducers que hace las cosas de una manera más adecuada para sus propias necesidades.

Ya que estamos hablando de patrones y separación de preocupaciones, en realidad me encuentro con algo relacionado, pero con acciones.

Es decir, el patrón ajax shouldFetch que veo en varios lugares parece ser un acoplamiento estrecho con la forma del estado. Por ejemplo, aquí . En ese ejemplo, ¿qué pasa si reutilizo el reductor pero no tengo el objeto de las entidades en la raíz del estado? Entonces esa acción fracasaría. ¿Cuál es la recomendación aquí? ¿Debo agregar acciones específicas de casos? ¿O una acción que acepta un selector?

@shadowii Me parece bien que un creador de acciones importe un selector. Colocaría selectores con reductores para que el conocimiento sobre la forma del estado se localice en esos archivos.

¿Cuál es el problema de que diferentes reductores manejen el mismo tipo de acción?
Por ejemplo, si tengo reductores account y auth , ambos manejan el tipo de acción LOGIN_SUCCESS (con el token de autenticación y los datos de la cuenta en la carga útil), cada uno uno actualiza solo la porción de estado que administran. Me he encontrado usando este enfoque varias veces cuando una acción afecta a diferentes partes del estado.

¿Cuál es el problema de que diferentes reductores manejen el mismo tipo de acción?

No hay problema, es un patrón muy común y útil. De hecho, es la razón por la que existen las acciones: si las acciones se asignan a los reductores 1: 1, no tendría mucho sentido tener acciones.

@ rhys-vdw intenté usar el middleware que publicaste.

customMiddleare.js

const customMiddleware =  store => next => action => {
  next({ ...action, getState: store.getState });
};

y en la creación de mi tienda tengo

import customMiddleware from './customMiddleware';

var store = createStore(
        rootReducer,
        initialState,
        applyMiddleware(
            reduxPromise,
            thunkMiddleware,
            loggerMiddleware,
            customMiddleware
        )
    );
return store;

Estoy teniendo el siguiente error:

applyMiddleware.js? ee15: 49 TypeError no detectado: el middleware no es una función (…)
(función anónima) @ applyMiddleware.js? ee15: 49
(función anónima) @ applyMiddleware.js? ee15: 48
createStore @ createStore.js? fe4c: 65
configureStore @ configureStore.js? ffde: 45
(función anónima) @ index.jsx? fdd7: 25
(función anónima) @ bundle.js: 1639
webpack_require @ bundle.js: 556
fn @ bundle.js: 87 (función anónima) @ bundle.js: 588
webpack_require @ bundle.js: 556
(función anónima) @ bundle.js: 579
(función anónima) @ bundle.js: 582

@ mars76 : Lo más probable es que no esté importando algo correctamente. Si ese es realmente el contenido completo de customMiddleware.js , entonces no está exportando _ nada_. En general, está pasando cuatro middlewares a applyMiddleware , y presumiblemente uno de ellos no es una referencia válida. Retroceda, elimínelos todos, vuelva a agregar uno a la vez, vea cuál no es válido y descubra por qué. Verifique las declaraciones de importación y exportación, verifique dos veces cómo se supone que debe inicializar las cosas, etc.

Hola,

Gracias @markerikson

Convertí la sintaxis de la siguiente manera y ahora está bien:

customMiddleware.js

export function customMiddleware(store) { return function (next) { return function (action) { next(Object.assign({}, action, { getState: store.getState })); }; }; } console.log(customMiddleware);

Sin embargo, intento acceder a la tienda en Action Creator (no en Reducer). Necesito hacer una llamada Async y necesito pasar diferentes partes del estado de la tienda administradas por varios reductores.

Esto es lo que estoy haciendo. No estoy seguro de si este es el enfoque correcto.

Envío una acción con los datos y dentro del reductor ahora tengo acceso a todo el Estado y estoy extrayendo las partes necesarias que necesito y creando un Objeto y enviando otra acción con estos datos que estarán disponibles en mi Action Creator donde Estoy haciendo una llamada Asyn usando esos datos.

Mi mayor preocupación es que los Reductores ahora están llamando a los métodos de creación de acciones.

Aprecio cualquier comentario.

Gracias

@ mars76 : de nuevo, _nunca _ debería intentar despachar desde un Reductor.

Para acceder a la tienda dentro de sus creadores de acciones, puede usar el middleware redux-thunk . También es posible que desee leer la pregunta de preguntas frecuentes sobre el intercambio de datos entre reductores , así como una esencia que escribí con algunos ejemplos de usos comunes de procesador .

Muchas gracias @markerikson.

Mi mal No me di cuenta de getState () en redux-thunk. Eso hace mi trabajo mucho más fácil y deja puros mis reductores.

@gaearon cuando mencionaste que un creador de acciones importaba un selector , ¿ pensabas que todo el estado se pasaría al creador de la acción para que lo transfiriera al selector, o me falta algo?

@tacomanator : FYI, Dan no suele responder a pings aleatorios en problemas de Redux en estos días. Demasiadas otras prioridades.

La mayoría de las veces, si está utilizando un selector en un creador de acciones, lo está haciendo dentro de un procesador. Los thunks tienen acceso a getState , por lo que se puede acceder al árbol de estado completo:

import {someSelector} from "./selectors";

function someThunk(someParameter) {
    return (dispatch, getState) => {
        const specificData = someSelector(getState(), someParameter);

        dispatch({type : "SOME_ACTION", payload : specificData});    
    }
}

@markerikson gracias, eso tiene sentido. Para su información, la razón por la que pregunto es principalmente sobre el uso de los datos derivados de los selectores para la lógica de negocios de validación en lugar de enviarlos a un reductor, pero eso también me da que pensar.

Un truco rápido que usé para combinar reductores en uno solo fue

const reducers = [ reducerA, reducerB, ..., reducerZ ];

let reducer = ( state = initialState, action ) => {
    return reducers.reduce( ( state, reducerFn ) => (
        reducerFn( state, action )
    ), state );
};

donde reducerA a reducerZ son las funciones reductoras individuales.

@brianpkelley : sí, eso es básicamente https://github.com/acdlite/reduce-reducers .

@markerikson Ah, bueno, la primera vez que

Je, seguro.

Para su información, es posible que desee leer las preguntas frecuentes: "¿Tengo que usar combineReducers ?" y las secciones Estructuración de reductores en los documentos. Además, mi serie de tutoriales "Practical Redux" tiene un par de publicaciones que demuestran algunas técnicas avanzadas de reducción (consulte la Parte 7 y la Parte 8).

Mis dos centavos cuando uso la lógica redux es aumentar la acción desde el estado raíz en la función transform .

Ejemplo:

const formInitLogic = createLogic({
  type: 'FORM_INIT',
  transform({ getState, action }, next) {
    const state = getState();
    next({
      ...action,
      payload: {
        ...action.payload,
        property1: state.branch1.someProperty > 5,
        property2: state.branch1.anotherProperty + state.branch2.yetAnotherProperty,
      },
    });
  },
});

Creo que se trata más de la estructura del código:
"no acceda directamente a las acciones del subestado"
después de usar redux en muchos proyectos, encontré que la mejor estructura de código es tratar con cada subestado como un componente completamente separado, cada uno de estos componentes tiene su propia carpeta y lógica, para implementar esta idea utilizo la siguiente estructura de archivos:

  • raíz de redux

    • reducers.js exporta

    • middleware.js export applyMiddleware para todos los middleware

    • configureStore.js createStore usando (reducers.js, middleware.js)

    • actions.js exporta acciones que envían acciones desde carpetas de estados <== ESTO

    • estado1

    • initial.js este archivo contiene el estado inicial (yo uso inmutable para crear un registro)

    • action-types.js este archivo contiene los tipos de acción (yo uso dacho como clave espejo)

    • actions.js este archivo contiene las acciones de estado

    • reducer.js este archivo contiene el reductor de estado

    • estado2

    • initial.js

    • action-types.js

      ....

Por qué ?
1- existen 2 tipos de acciones en cada aplicación:

  • acciones de estado (redux / state1 / actions.js) estas acciones solo tienen la responsabilidad de cambiar el estado, no debe activar estas acciones directamente, siempre use acciones de la aplicación

    • acciones de la aplicación (redux / actions.js) estas acciones tienen la responsabilidad de hacer algo de lógica de la aplicación y pueden activar acciones de estado

2- adicionalmente usando esta estructura puedes crear un nuevo estado simplemente copiando pegar una carpeta y renombrarla;)

¿Qué pasa cuando 2 reductores necesitan cambiar la misma propiedad?

Digamos que tengo un reductor que me gustaría dividir en diferentes archivos y que hay una propiedad llamada 'error', que cambio cuando las solicitudes http fallan por una razón u otra.

Ahora, en algún momento, el reductor se ha vuelto demasiado grande, así que decidí dividirlo en varios reductores, pero todos necesitan cambiar esa propiedad de 'error'.

¿Y que?

@ Wassap124 No es posible que dos reductores gestionen el mismo estado. ¿Quiere decir que dos acciones deben cambiar la misma propiedad?

Sin más detalles, es posible que prefiera utilizar un procesador para enviar una acción de "error de configuración".

@ rhys-vdw Estoy un poco atascado tratando de dividir un reductor bastante largo.
Y con respecto a su sugerencia, ¿no contradice eso la regla de no tener efectos secundarios?

@ Wassap124 , @ rhys-vdw: para aclarar, no es posible que dos reductores administren el mismo estado _ si solo está usando combineReducers _. Sin embargo, ciertamente puede escribir otra lógica de reducción que se ajuste a su propio caso de uso; consulte https://redux.js.org/recipes/structuringreducers/beyondcombinereducers .

Si haces alguna especulación de fuente rápida ...

El combineReducers devuelve una firma de función así

return function combination(state = {}, action) {

ver https://github.com/reduxjs/redux/blob/master/src/combineReducers.js#L145

después de crear un cierre para hacer referencia al objeto reductor real.

Por lo tanto, puede agregar algo simple como eso para manejar acciones globalmente en el caso de necesitar hidratar y recargar el estado del almacenamiento.

const globalReducerHandler = () => {
  return (state = {}, action) => {
    if (action.type === 'DO_GLOBAL_THING') {
      return globallyModifyState(state)
    }
    return reducers(state, action)
  }
}

Esto puede ser o no un anti patrón, pero el pragmatismo siempre ha sido más importante.

En lugar de llamar a un reductor desde otro reductor (que es un antipatrón, porque globalReducerHandler no tiene nada que ver con el reductor que llama), use reduceReducers , que es esencialmente compose para reductores (bueno, es literalmente composición en una mónada (Action ->) ).

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

olalonde picture olalonde  ·  3Comentarios

benoneal picture benoneal  ·  3Comentarios

ms88privat picture ms88privat  ·  3Comentarios

CellOcean picture CellOcean  ·  3Comentarios

vraa picture vraa  ·  3Comentarios