Estoy convirtiendo un modelo de estado existente a Redux y ha sido sencillo en su mayor parte. Sin embargo, el único punto con el que tengo problemas es la conversión de solicitudes ajax de estado "observadas". Esencialmente, tengo ciertas solicitudes de ajax "vinculadas" a otras partes del estado, por lo que no importa quién las modifique, siempre se emitirán correctamente. Puedo obtener un comportamiento similar suscribiéndome a las actualizaciones de la tienda Redux, pero activar acciones en el oyente se siente como un truco.
Una posible solución es mover la lógica al creador de la acción a través del patrón thunk. El problema es que tendría que duplicar la lógica de búsqueda en las acciones (ya que varias acciones podrían modificar el estado "observado") o llevar la mayoría de la lógica reductora al nivel del creador de la acción. El creador de la acción tampoco debe saber cómo responderán los reductores a las acciones emitidas.
Podría agrupar "subacciones", por lo que solo necesito colocar la lógica de obtención adecuada en cada "bloque" de acción, pero esto parece violar el concepto de acciones que producen un estado válido. Prefiero tener esta responsabilidad a nivel del creador de la acción.
¿Hay alguna regla generalmente aceptada en torno a esto? Esta no es una aplicación simple donde se realizan solicitudes ajax ad hoc a medida que interactúan los componentes, la mayoría de los datos se comparten entre múltiples componentes y las solicitudes se optimizan y obtienen en reacción al cambio de estado.
TLDR; Quiero disparar solicitudes ajax en respuesta a cambios en el estado, no cuando ocurre una acción específica. ¿Existe una forma mejor, "específica de Redux" de organizar action/actionCreators para simular este comportamiento, además de activar estas acciones en un oyente de suscripción?
store.subscribe() La forma más fácil es simplemente usar el método store.subscribe() :
let prevState store.subscribe(() => { let state = store.getState() if (state.something !== prevState.something) { store.dispatch(something()) } prevState = state })Puede escribir una abstracción personalizada que le permita registrar las condiciones de los efectos secundarios para que se expresen de forma más declarativa.
Es posible que desee ver Redux Loop , que le permite describir llamadas de efectos (como AJAX) junto con actualizaciones de estado en sus reductores.
De esta manera, puede "devolver" esos efectos en respuesta a ciertas acciones al igual que actualmente return el siguiente estado:
export default function reducer(state, action) { switch (action.type) { case 'LOADING_START': return loop( { ...state, loading: true }, Effects.promise(fetchDetails, action.payload.id) ); case 'LOADING_SUCCESS': return { ...state, loading: false, details: action.payload };Este enfoque está inspirado en la Arquitectura Elm .
También puede usar Redux Saga que le permite escribir procesos de ejecución prolongada ("sagas") que pueden tomar acciones, realizar algún trabajo asincrónico y poner acciones de resultados en la tienda. Las sagas miran acciones específicas en lugar de actualizaciones de estado, que no es lo que pediste, pero pensé que aún las mencionaría por si acaso. Funcionan muy bien para el flujo de control asincrónico complicado y la concurrencia.
function* fetchUser(action) { try { const user = yield call(Api.fetchUser, action.payload.userId); yield put({type: "USER_FETCH_SUCCEEDED", user: user}); } catch (e) { yield put({type: "USER_FETCH_FAILED",message: e.message}); } } function* mySaga() { yield* takeEvery("USER_FETCH_REQUESTED", fetchUser); }Todas estas opciones tienen diferentes compensaciones. A veces, las personas usan uno o dos, o incluso los tres, según lo que resulte más conveniente para probar y describir la lógica necesaria. Lo animo a que pruebe los tres y elija el que funcione mejor para su caso de uso.
Puede usar un middleware para activar sus acciones remotas en respuesta a la acción local.
Digamos que tengo una acción local:
const updateField = (val) => { {type: UPDATE_FIELD, val} }Y un campo de entrada con:
<input type='text' onChange={this.props.updateField.bind(this.val)}>Entonces, en pocas palabras, cuando escribe dentro del campo, activa su acción que a su vez cambia el estado a través del reductor. Olvidemos cómo se pasó esta acción al componente o qué es this.val ; simplemente asumimos que esto ya se resolvió y está funcionando.
Todo está bien con esta configuración, pero solo cambia su estado localmente. Para actualizar el servidor, deberá realizar otra acción. Vamos a construirlo:
const updateFieldOnServer = (val) => { return (dispatch) => { MAKE_AJAX.done( FIRE_SOME_ACTIONS_ON_SUCCESS ).failure( FIRE_SOME_ACTIONS_ON_FAILURE ) } }Esta es solo una simple acción asincrónica de procesador que de alguna manera hace una solicitud ajax, devuelve promesas y hace algo más en caso de éxito o fracaso.
Entonces, el problema que tenemos ahora es que quiero que ambas acciones se disparen cuando cambie el estado de mi entrada, pero no puedo hacer que onChange tome dos funciones. Así que crearé un middleware llamado ServerUpdatesMiddleware
import _ from 'lodash' import { UPDATE_FIELD, } from 'actionsPath' export default ({ dispatch }) => next => action => { if(_.includes([UPDATE_FIELD], action.type)){ switch(action.type){ case UPDATE_FIELD: dispatch(updateFieldOnServer(action.val)) } } return next(action) }Puedo agregarlo a mi pila:
import ServerUpdatesMiddleware from 'pathToMe' const createStoreWithMiddleware = applyMiddleware( ServerUpdatesMiddleware, thunkMiddleware, logger )(createStore);Y en este momento, cada vez que se envíe la acción updateField , se enviará automáticamente la acción updateFieldOnServer .
Este es solo un ejemplo que creo que describirá el problema fácilmente: este problema se puede solucionar de muchas otras maneras diferentes, pero creo que se ajusta muy bien a los requisitos. Así es como hago las cosas, espero que te ayude.
Utilizo middlewares todo el tiempo y tengo muchos de ellos; nunca tuve ningún problema con este enfoque y simplifica la lógica de la aplicación; solo tiene que buscar en un solo lugar para averiguar qué está pasando.
Tener módulos que se suscriban a las actualizaciones de estado y el lanzamiento de solicitudes Ajax (activar acciones sobre la marcha) me parece bien, ya que pone a las tiendas/reductores firmemente a cargo de activar solicitudes. En mi aplicación grande, TODAS las solicitudes de Ajax y otros comportamientos asíncronos se realizan de esta manera, por lo que todas las acciones pueden ser solo cargas útiles, sin el concepto de 'creadores de acciones'.
Si es posible, evite las acciones de sincronización en cascada. Mis controladores asincrónicos nunca activan acciones sincrónicamente, sino solo una vez que se completa la solicitud.
En mi opinión, este es un enfoque mucho más funcional que los creadores de acciones asincrónicas, ¡que puede que prefieras o no!