Según los documentos, "Sin middleware, la tienda Redux solo admite el flujo de datos síncrono" . No entiendo por qué este es el caso. ¿Por qué el componente contenedor no puede llamar a la API asíncrona y luego a dispatch
las acciones?
Por ejemplo, imagine una interfaz de usuario simple: un campo y un botón. Cuando el usuario presiona el botón, el campo se llena con datos de un servidor remoto.
import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';
const ActionTypes = {
STARTED_UPDATING: 'STARTED_UPDATING',
UPDATED: 'UPDATED'
};
class AsyncApi {
static getFieldValue() {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve(Math.floor(Math.random() * 100));
}, 1000);
});
return promise;
}
}
class App extends React.Component {
render() {
return (
<div>
<input value={this.props.field}/>
<button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
{this.props.isWaiting && <div>Waiting...</div>}
</div>
);
}
}
App.propTypes = {
dispatch: React.PropTypes.func,
field: React.PropTypes.any,
isWaiting: React.PropTypes.bool
};
const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
switch (action.type) {
case ActionTypes.STARTED_UPDATING:
return { ...state, isWaiting: true };
case ActionTypes.UPDATED:
return { ...state, isWaiting: false, field: action.payload };
default:
return state;
}
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
(state) => {
return { ...state };
},
(dispatch) => {
return {
update: () => {
dispatch({
type: ActionTypes.STARTED_UPDATING
});
AsyncApi.getFieldValue()
.then(result => dispatch({
type: ActionTypes.UPDATED,
payload: result
}));
}
};
})(App);
export default class extends React.Component {
render() {
return <Provider store={store}><ConnectedApp/></Provider>;
}
}
Cuando se procesa el componente exportado, puedo hacer clic en el botón y la entrada se actualiza correctamente.
Tenga update
en cuenta la función en la connect
llamada. Envía una acción que le dice a la aplicación que se está actualizando y luego realiza una llamada asíncrona. Una vez que finaliza la llamada, el valor proporcionado se distribuye como una carga útil de otra acción.
¿Qué hay de malo en este enfoque? ¿Por qué querría usar Redux Thunk o Redux Promise, como sugiere la documentación?
EDITAR: Busqué pistas en el repositorio de Redux y descubrí que los creadores de acción debían ser funciones puras en el pasado. Por ejemplo, aquí hay un usuario que intenta proporcionar una mejor explicación para el flujo de datos asíncrono:
El creador de la acción en sí sigue siendo una función pura, pero la función thunk que devuelve no necesita serlo, y puede hacer nuestras llamadas asíncronas
Los creadores de acciones ya no están obligados a ser puros. Entonces, el middleware thunk / promise definitivamente se requería en el pasado, pero parece que este ya no es el caso.
fuente
Respuestas:
No hay nada malo con este enfoque. Es simplemente inconveniente en una aplicación grande porque tendrás diferentes componentes que realizan las mismas acciones, es posible que desees eliminar algunas acciones o mantener algún estado local como ID de incremento automático cerca de los creadores de acciones, etc. Por lo tanto, es más fácil El punto de vista de mantenimiento para extraer creadores de acciones en funciones separadas.
Puede leer mi respuesta a "Cómo enviar una acción de Redux con un tiempo de espera" para obtener un tutorial más detallado.
El middleware como Redux Thunk o Redux Promise solo le da "azúcar de sintaxis" para enviar thunks o promesas, pero no tiene que usarlo.
Entonces, sin ningún middleware, su creador de acción podría verse así
Pero con Thunk Middleware puedes escribirlo así:
Entonces no hay una gran diferencia. Una cosa que me gusta del último enfoque es que al componente no le importa que el creador de la acción sea asíncrono. Simplemente llama
dispatch
normalmente, también se puede usarmapDispatchToProps
para vincular a tal creador de acción con una sintaxis corta, etc. Los componentes no saben cómo se implementan los creadores de acción, y puede cambiar entre diferentes enfoques asincrónicos (Redux Thunk, Redux Promise, Redux Saga ) sin cambiar los componentes. Por otro lado, con el primer enfoque explícito, sus componentes saben exactamente que una llamada específica es asíncrona y debedispatch
ser aprobada por alguna convención (por ejemplo, como un parámetro de sincronización).También piense en cómo cambiará este código. Supongamos que queremos tener una segunda función de carga de datos y combinarlos en un solo creador de acciones.
Con el primer enfoque, debemos tener en cuenta a qué tipo de creador de acción estamos llamando:
Con Redux Thunk, los creadores de acciones pueden ser
dispatch
el resultado de otros creadores de acciones y ni siquiera pensar si son síncronos o asíncronos:Con este enfoque, si luego desea que sus creadores de acciones analicen el estado actual de Redux, puede usar el segundo
getState
argumento pasado a los thunks sin modificar el código de llamada en absoluto:Si necesita cambiarlo para que sea sincrónico, también puede hacerlo sin cambiar ningún código de llamada:
Entonces, el beneficio de usar middleware como Redux Thunk o Redux Promise es que los componentes no son conscientes de cómo se implementan los creadores de acciones, y si les importa el estado de Redux, si son síncronos o asíncronos, y si llaman o no a otros creadores de acciones . La desventaja es un poco indirecta, pero creemos que vale la pena en aplicaciones reales.
Finalmente, Redux Thunk y sus amigos son solo un posible enfoque para las solicitudes asincrónicas en las aplicaciones de Redux. Otro enfoque interesante es Redux Saga, que le permite definir demonios de larga duración ("sagas") que toman acciones a medida que se presentan y transforman o realizan solicitudes antes de generar acciones. Esto mueve la lógica de los creadores de acción a las sagas. Es posible que desee verlo y luego elegir lo que más le convenga.
Esto es incorrecto. Los documentos dijeron esto, pero los documentos estaban equivocados.
Los creadores de acciones nunca tuvieron que ser funciones puras.
Arreglamos los documentos para reflejar eso.
fuente
alert
despuésdispatch()
de la acción.loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
. ¿Por qué necesito pasar en despacho? Si por convención solo hay una única tienda global, ¿por qué no hago referencia a eso directamente y lo hagostore.dispatch
cada vez que necesito, por ejemplo, entrarloadData
?store
instancia diferente para cada solicitud para que no puedas definirla de antemano.Usted no
Pero ... deberías usar redux-saga :)
La respuesta de Dan Abramov es correcta,
redux-thunk
pero hablaré un poco más sobre redux-saga, que es bastante similar pero más poderosa.Imperativo VS declarativo
redux-thunk
es imperativo /redux-saga
es declarativoCuando tienes un golpe en tus manos, como una mónada de IO o una promesa, no puedes saber fácilmente qué hará una vez que ejecutes. La única forma de probar un thunk es ejecutarlo y burlarse del despachador (o de todo el mundo exterior si interactúa con más cosas ...).
Si está utilizando simulacros, entonces no está haciendo una programación funcional.
Fuente
Las sagas (como se implementaron
redux-saga
) son declarativas y, al igual que la mónada libre o los componentes Reaccionar, son mucho más fáciles de probar sin ninguna simulación.Ver también este artículo :
(En realidad, Redux-saga es como un híbrido: el flujo es imperativo pero los efectos son declarativos)
Confusión: acciones / eventos / comandos ...
Hay mucha confusión en el mundo frontend sobre cómo algunos conceptos de backend como CQRS / EventSourcing y Flux / Redux pueden estar relacionados, principalmente porque en Flux usamos el término "acción" que a veces puede representar tanto el código imperativo (
LOAD_USER
) como los eventos (USER_LOADED
) Creo que, al igual que el abastecimiento de eventos, solo debe enviar eventos.Usando sagas en la práctica
Imagine una aplicación con un enlace a un perfil de usuario. La forma idiomática de manejar esto con cada middleware sería:
redux-thunk
redux-saga
Esta saga se traduce en:
Como puede ver, hay algunas ventajas de
redux-saga
.El uso de
takeLatest
permisos para expresar que solo está interesado en obtener los datos del último nombre de usuario en el que se hizo clic (maneje los problemas de concurrencia en caso de que el usuario haga clic muy rápido en muchos nombres de usuario). Este tipo de cosas es difícil con los thunks. Podría haberlo usadotakeEvery
si no desea este comportamiento.Mantienes a los creadores de acción puros. Tenga en cuenta que todavía es útil mantener actionCreators (en sagas
put
y componentesdispatch
), ya que podría ayudarlo a agregar validación de acciones (aserciones / flujo / mecanografiado) en el futuro.Su código se vuelve mucho más comprobable ya que los efectos son declarativos
Ya no necesita activar llamadas de tipo rpc como
actions.loadUser()
. Su interfaz de usuario solo necesita enviar lo que HA SUCEDIDO. Solo disparamos eventos (¡siempre en tiempo pasado!) Y ya no acciones. Esto significa que puede crear "patos" desacoplados o contextos limitados y que la saga puede actuar como el punto de acoplamiento entre estos componentes modulares.Esto significa que sus vistas son más fáciles de administrar porque ya no necesitan contener esa capa de traducción entre lo que sucedió y lo que debería suceder como efecto
Por ejemplo, imagine una vista de desplazamiento infinita.
CONTAINER_SCROLLED
puede conducir aNEXT_PAGE_LOADED
, pero ¿es realmente responsabilidad del contenedor desplazable decidir si debemos cargar o no otra página? Entonces, debe estar al tanto de cosas más complicadas, como si la última página se cargó correctamente o si ya hay una página que intenta cargarse, o si no quedan más elementos para cargar. No lo creo: para una reutilización máxima, el contenedor desplazable solo debe describir que se ha desplazado. La carga de una página es un "efecto comercial" de ese desplazamientoAlgunos podrían argumentar que los generadores pueden ocultar inherentemente el estado fuera de la tienda redux con variables locales, pero si comienzas a orquestar cosas complejas dentro de thunks iniciando temporizadores, etc., de todos modos, tendrías el mismo problema. Y hay un
select
efecto que ahora permite obtener algún estado de su tienda Redux.Las sagas pueden viajar en el tiempo y también permiten un registro de flujo complejo y herramientas de desarrollo en las que se está trabajando actualmente. Aquí hay algunos registros de flujo asíncrono simples que ya están implementados:
Desacoplamiento
Las sagas no solo están reemplazando los thunks redux. Vienen de backend / sistemas distribuidos / abastecimiento de eventos.
Es un error muy común pensar que las sagas están aquí para reemplazar sus redux thunks con una mejor capacidad de prueba. En realidad, esto es solo un detalle de implementación de redux-saga. El uso de efectos declarativos es mejor que los thunks para la comprobabilidad, pero el patrón de saga se puede implementar sobre el código imperativo o declarativo.
En primer lugar, la saga es una pieza de software que permite coordinar transacciones de larga duración (consistencia eventual) y transacciones en diferentes contextos delimitados (jerga de diseño dirigida por el dominio).
Para simplificar esto para el mundo frontend, imagine que hay widget1 y widget2. Cuando se hace clic en algún botón en widget1, entonces debería tener un efecto en widget2. En lugar de acoplar los 2 widgets (es decir, widget1 despacha una acción que se dirige a widget2), widget1 solo despacha que se hizo clic en su botón. Luego, la saga escuche este botón, haga clic y luego actualice widget2 presentando un nuevo evento que widget2 conoce.
Esto agrega un nivel de indirección que es innecesario para aplicaciones simples, pero hace que sea más fácil escalar aplicaciones complejas. Ahora puede publicar widget1 y widget2 en diferentes repositorios npm para que nunca tengan que conocerse, sin tener que compartir un registro global de acciones. Los 2 widgets ahora son contextos limitados que pueden vivir por separado. No se necesitan mutuamente para ser coherentes y también se pueden reutilizar en otras aplicaciones. La saga es el punto de acoplamiento entre los dos widgets que los coordinan de manera significativa para su negocio.
Algunos buenos artículos sobre cómo estructurar su aplicación Redux, en los que puede usar Redux-saga por motivos de desacoplamiento:
Un caso de uso concreto: sistema de notificación
Quiero que mis componentes puedan activar la visualización de notificaciones en la aplicación. Pero no quiero que mis componentes estén altamente acoplados al sistema de notificación que tiene sus propias reglas comerciales (máximo 3 notificaciones mostradas al mismo tiempo, cola de notificaciones, tiempo de visualización de 4 segundos, etc.).
No quiero que mis componentes JSX decidan cuándo se mostrará / ocultará una notificación. Solo le doy la posibilidad de solicitar una notificación y dejar las complejas reglas dentro de la saga. Este tipo de cosas es bastante difícil de implementar con thunks o promesas.
He descrito aquí cómo se puede hacer esto con la saga
¿Por qué se llama una saga?
El término saga proviene del mundo del backend. Inicialmente presenté a Yassine (el autor de Redux-saga) a ese término en una larga discusión .
Inicialmente, ese término se introdujo con un documento , se suponía que el patrón de la saga se usaría para manejar la consistencia eventual en las transacciones distribuidas, pero su uso se ha extendido a una definición más amplia por parte de los desarrolladores de back-end para que ahora también cubra el "administrador de procesos" patrón (de alguna manera el patrón de saga original es una forma especializada de administrador de procesos).
Hoy, el término "saga" es confuso ya que puede describir 2 cosas diferentes. Como se usa en redux-saga, no describe una forma de manejar transacciones distribuidas sino más bien una forma de coordinar acciones en su aplicación.
redux-saga
También podría haber sido llamadoredux-process-manager
.Ver también:
Alternativas
Si no le gusta la idea de usar generadores pero le interesa el patrón de saga y sus propiedades de desacoplamiento, también puede lograr lo mismo con redux-observable que usa el nombre
epic
para describir exactamente el mismo patrón, pero con RxJS. Si ya está familiarizado con Rx, se sentirá como en casa.Algunos recursos útiles de redux-saga
2017 aconseja
yield put(someActionThunk)
si tiene sentido.Si tiene miedo de usar Redux-saga (o Redux-observable) pero solo necesita el patrón de desacoplamiento, verifique redux-dispatch-subscribe : permite escuchar despachos y desencadenar nuevos despachos en el oyente.
fuente
ADD_ITEM
, es imprescindible porque despacha una acción que tiene como objetivo tener un efecto en su tienda: espera que la acción haga algo. Siendo declarativo, abrace la filosofía del abastecimiento de eventos: no despacha acciones para activar cambios en sus aplicaciones, sino que despacha eventos pasados para describir lo que sucedió en su aplicación. El envío de un evento debería ser suficiente para considerar que el estado de la aplicación ha cambiado. El hecho de que haya una tienda Redux que reaccione al evento es un detalle de implementación opcionalLa respuesta corta : me parece un enfoque totalmente razonable para el problema de la asincronía. Con un par de advertencias.
Tenía una línea de pensamiento muy similar cuando trabajaba en un nuevo proyecto que recién comenzamos en mi trabajo. Era un gran admirador del elegante sistema de Vanilla Redux para actualizar la tienda y volver a entregar los componentes de una manera que se mantiene fuera de las entrañas de un árbol de componentes React. Me pareció extraño conectarme a ese elegante
dispatch
mecanismo para manejar la asincronía.Terminé con un enfoque realmente similar a lo que tienes allí en una biblioteca que factoré a partir de nuestro proyecto, que llamamos react-redux-controller .
Terminé sin seguir el enfoque exacto que tienes arriba por un par de razones:
dispatch
sí mismas a través del alcance léxico. Esto limita las opciones para refactorizar una vez que esaconnect
declaración se sale de control, y se ve bastante difícil de manejar con ese únicoupdate
método. Por lo tanto, necesita algún sistema que le permita componer esas funciones de despachador si las divide en módulos separados.En conjunto, debe armar algún sistema para permitir que
dispatch
la tienda se inyecte en sus funciones de despacho, junto con los parámetros del evento. Sé de tres enfoques razonables para esta inyección de dependencia:dispatch
enfoques de middleware, pero supongo que son básicamente los mismos.connect
, en lugar de tener que trabajar directamente con la tienda sin procesar y normalizada.this
contexto, a través de una variedad de mecanismos posibles.Actualizar
Se me ocurre que parte de este enigma es una limitación de react-redux . El primer argumento para
connect
obtener una instantánea del estado, pero no el envío. El segundo argumento se despacha pero no el estado. Ninguno de los argumentos obtiene un thunk que se cierra sobre el estado actual, por poder ver el estado actualizado en el momento de una continuación / devolución de llamada.fuente
El objetivo de Abramov, y todos lo ideal, es simplemente encapsular la complejidad (y las llamadas asíncronas) en el lugar donde sea más apropiado .
¿Dónde está el mejor lugar para hacer eso en el flujo de datos estándar de Redux? Qué tal si:
fuente
Para responder la pregunta que se hace al principio:
Tenga en cuenta que esos documentos son para Redux, no para Redux más React. Las tiendas Redux conectadas a los componentes React pueden hacer exactamente lo que usted dice, pero una tienda Plain Jane Redux sin middleware no acepta argumentos
dispatch
excepto objetos simples.Sin middleware, por supuesto, aún podría hacerlo
Pero es un caso similar donde la asincronía se envuelve alrededor de Redux en lugar de ser manejada por Redux. Entonces, el middleware permite la asincronía modificando a qué se puede pasar directamente
dispatch
.Dicho esto, el espíritu de su sugerencia es, creo, válido. Ciertamente, hay otras formas de manejar la asincronía en una aplicación Redux + React.
Una ventaja de usar middleware es que puedes seguir usando creadores de acción de forma normal sin preocuparte exactamente de cómo están conectados. Por ejemplo, usando
redux-thunk
, el código que escribió se parecería mucho aque no se ve tan diferente del original, simplemente se baraja un poco, y
connect
no sabe queupdateThing
es (o necesita ser) asíncrono.Si también desea apoyar promesas , observables , sagas o creadores de acción personalizados y altamente declarativos , Redux puede hacerlo simplemente cambiando lo que le pasa
dispatch
(también conocido como lo que devuelve de los creadores de acción). No es necesario muckear con los componentes React (oconnect
llamadas).fuente
OK, comencemos a ver cómo funciona el middleware primero, eso responde bastante a la pregunta, este es el código fuente de una función pplyMiddleWare en Redux:
Mire esta parte, vea cómo nuestro despacho se convierte en una función .
OK, así es como se presenta Redux-thunk como uno de los middlewares más utilizados para Redux:
Entonces, como puede ver, devolverá una función en lugar de una acción, lo que significa que puede esperar y llamarla cuando lo desee, ya que es una función ...
Entonces, ¿qué diablos es thunk? Así se introdujo en Wikipedia:
Entonces, vea cuán fácil es el concepto y cómo puede ayudarlo a administrar sus acciones asíncronas ...
Eso es algo que puedes vivir sin él, pero recuerda que en la programación siempre hay formas mejores, más ordenadas y adecuadas de hacer las cosas ...
fuente
Usar Redux-saga es el mejor middleware en la implementación de React-redux.
Ej: store.js
Y luego saga.js
Y luego action.js
Y luego reducer.js
Y luego main.js
prueba esto ... está funcionando
fuente
Hay creadores de acciones sincrónicas y luego hay creadores de acciones asincrónicas.
Un creador de acciones sincrónicas es aquel que, cuando lo llamamos, devuelve inmediatamente un objeto de Acción con todos los datos relevantes adjuntos a ese objeto y está listo para ser procesado por nuestros reductores.
Los creadores de acciones asincrónicas requieren de un poco de tiempo antes de estar listos para enviar una acción.
Por definición, cada vez que tenga un creador de acciones que realice una solicitud de red, siempre calificará como creador de acciones asíncronas.
Si desea tener creadores de acciones asincrónicas dentro de una aplicación Redux, debe instalar algo llamado middleware que le permitirá tratar con esos creadores de acciones asincrónicas.
Puede verificar esto en el mensaje de error que nos dice que usemos middleware personalizado para acciones asíncronas.
Entonces, ¿qué es un middleware y por qué lo necesitamos para el flujo asíncrono en Redux?
En el contexto de middleware redux como redux-thunk, un middleware nos ayuda a tratar con creadores de acciones asíncronas, ya que eso es algo que Redux no puede manejar de inmediato.
Con un middleware integrado en el ciclo de Redux, todavía estamos llamando a los creadores de acciones, que devolverán una acción que se enviará, pero ahora cuando despachemos una acción, en lugar de enviarla directamente a todos nuestros reductores, vamos para decir que se enviará una acción a través de todos los middleware diferentes dentro de la aplicación.
Dentro de una sola aplicación Redux, podemos tener tantos o tan pocos middleware como queramos. En su mayor parte, en los proyectos en los que trabajamos tendremos uno o dos middleware conectados a nuestra tienda Redux.
Un middleware es una función simple de JavaScript que se llamará con cada acción que despachemos. Dentro de esa función, un middleware tiene la oportunidad de detener el envío de una acción a cualquiera de los reductores, puede modificar una acción o simplemente perder el tiempo con una acción de cualquier manera que, por ejemplo, podríamos crear un middleware que registre la consola cada acción que envíes solo para tu placer visual.
Hay una gran cantidad de middleware de código abierto que puede instalar como dependencias en su proyecto.
No está limitado a utilizar solo middleware de código abierto o instalarlos como dependencias. Puede escribir su propio middleware personalizado y usarlo dentro de su tienda Redux.
Uno de los usos más populares del middleware (y obtener su respuesta) es tratar con creadores de acciones asincrónicas, probablemente el middleware más popular que existe es redux-thunk y se trata de ayudarlo a lidiar con creadores de acciones asincrónicas.
Existen muchos otros tipos de middleware que también lo ayudan a tratar con creadores de acciones asincrónicas.
fuente
Para responder la pregunta:
Yo diría por al menos dos razones:
La primera razón es la separación de preocupaciones, no es el trabajo de
action creator
llamarapi
y recuperar datos, debe pasar dos argumentos a suaction creator function
, elaction type
y apayload
.La segunda razón es porque
redux store
está esperando un objeto simple con el tipo de acción obligatoria y opcionalmente unpayload
(pero aquí también tiene que pasar la carga útil).El creador de la acción debe ser un objeto simple como el siguiente:
Y el trabajo de
Redux-Thunk midleware
quedispache
el resultado de suapi call
a la apropiadaaction
.fuente
Cuando se trabaja en un proyecto empresarial, hay muchos requisitos disponibles en middleware como (saga) no disponibles en flujo asíncrono simple, a continuación se detallan algunos:
La lista es larga, solo revise la sección avanzada en la documentación de la saga
fuente