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
- DOM : jQuery es imperativo / React es declarativo
- Mónadas : IO es imperativo / Free es declarativo
- Efectos de Redux :
redux-thunk
es imperativo / redux-saga
es declarativo
Cuando 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.
Visto a través de la lente de los efectos secundarios, los simulacros son una señal de que su código es impuro y, a los ojos del programador funcional, una prueba de que algo está mal. En lugar de descargar una biblioteca para ayudarnos a verificar que el iceberg esté intacto, deberíamos navegar por él. Un tipo duro de TDD / Java una vez me preguntó cómo te burlas en Clojure. La respuesta es, generalmente no lo hacemos. Generalmente lo vemos como una señal de que necesitamos refactorizar nuestro código.
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 la FP moderna, no deberíamos escribir programas, deberíamos escribir descripciones de los programas, que luego podemos introspectar, transformar e interpretar a voluntad.
(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
<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
);
}
redux-saga
<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>
function* loadUserProfileOnNameClick() {
yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}
function* fetchUser(action) {
try {
const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
yield put({ type: 'USER_PROFILE_LOADED', userProfile })
}
catch(err) {
yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
}
}
Esta saga se traduce en:
cada vez que se hace clic en un nombre de usuario, busque el perfil del usuario y luego envíe un evento con el perfil cargado.
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 usado takeEvery
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 componentes dispatch
), 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 a NEXT_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 desplazamiento
Algunos 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 llamado redux-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.
const loadUserProfileOnNameClickEpic = action$ =>
action$.ofType('USER_NAME_CLICKED')
.switchMap(action =>
Observable.ajax(`http://data.com/${action.payload.userId}`)
.map(userProfile => ({
type: 'USER_PROFILE_LOADED',
userProfile
}))
.catch(err => Observable.of({
type: 'USER_PROFILE_LOAD_FAILED',
err
}))
);
Algunos recursos útiles de redux-saga
2017 aconseja
- No abuses de Redux-saga solo por usarlo. Las llamadas de API comprobables solo no valen la pena.
- No elimine los thunks de su proyecto para la mayoría de los casos simples.
- No dude en enviar thunks
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.
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});