Se habla mucho sobre el último niño en la ciudad de redux en este momento, redux-saga / redux-saga . Utiliza funciones generadoras para escuchar / despachar acciones.
Antes de comprenderlo, me gustaría saber las ventajas y desventajas de usar en redux-saga
lugar del siguiente enfoque donde estoy usando redux-thunk
async / wait.
Un componente podría verse así, despachar acciones como de costumbre.
import { login } from 'redux/auth';
class LoginForm extends Component {
onClick(e) {
e.preventDefault();
const { user, pass } = this.refs;
this.props.dispatch(login(user.value, pass.value));
}
render() {
return (<div>
<input type="text" ref="user" />
<input type="password" ref="pass" />
<button onClick={::this.onClick}>Sign In</button>
</div>);
}
}
export default connect((state) => ({}))(LoginForm);
Entonces mis acciones se parecen a esto:
// auth.js
import request from 'axios';
import { loadUserData } from './user';
// define constants
// define initial state
// export default reducer
export const login = (user, pass) => async (dispatch) => {
try {
dispatch({ type: LOGIN_REQUEST });
let { data } = await request.post('/login', { user, pass });
await dispatch(loadUserData(data.uid));
dispatch({ type: LOGIN_SUCCESS, data });
} catch(error) {
dispatch({ type: LOGIN_ERROR, error });
}
}
// more actions...
// user.js
import request from 'axios';
// define constants
// define initial state
// export default reducer
export const loadUserData = (uid) => async (dispatch) => {
try {
dispatch({ type: USERDATA_REQUEST });
let { data } = await request.get(`/users/${uid}`);
dispatch({ type: USERDATA_SUCCESS, data });
} catch(error) {
dispatch({ type: USERDATA_ERROR, error });
}
}
// more actions...
javascript
reactjs
redux
redux-thunk
redux-saga
hampusohlsson
fuente
fuente
::
antes dethis.onClick
hacer?this
), también conocido comothis.onClick = this.onClick.bind(this)
. La forma más larga generalmente se recomienda hacer en el constructor, ya que la mano corta se vuelve a unir en cada render.bind()
mucho para pasarthis
a la función, pero comencé a usar() => method()
ahora.Respuestas:
En redux-saga, el equivalente del ejemplo anterior sería
Lo primero que debe notar es que estamos llamando a las funciones de la API usando el formulario
yield call(func, ...args)
.call
no ejecuta el efecto, solo crea un objeto simple como{type: 'CALL', func, args}
. La ejecución se delega al middleware redux-saga que se encarga de ejecutar la función y reanudar el generador con su resultado.La principal ventaja es que puede probar el generador fuera de Redux utilizando simples comprobaciones de igualdad
Tenga en cuenta que nos estamos burlando del resultado de la llamada api simplemente inyectando los datos simulados en el
next
método del iterador. Los datos de burla son mucho más simples que las funciones de burla.La segunda cosa a notar es la llamada a
yield take(ACTION)
. Thunks son llamados por el creador de la acción en cada nueva acción (por ejemploLOGIN_REQUEST
). es decir, las acciones son continuamente empujados a procesadores y procesadores no tienen ningún control sobre cuándo dejar de manejar esas acciones.En redux-saga, generadores de tirar de la siguiente acción. es decir, tienen control sobre cuándo escuchar alguna acción y cuándo no. En el ejemplo anterior, las instrucciones de flujo se colocan dentro de un
while(true)
bucle, por lo que escuchará cada acción entrante, que de alguna manera imita el comportamiento de empuje de golpe.El enfoque de extracción permite implementar flujos de control complejos. Supongamos, por ejemplo, que queremos agregar los siguientes requisitos
Manejar la acción del usuario LOGOUT
Tras el primer inicio de sesión exitoso, el servidor devuelve un token que caduca con algún retraso almacenado en un
expires_in
campo. Tendremos que actualizar la autorización en segundo plano cadaexpires_in
milisegundosTenga en cuenta que al esperar el resultado de las llamadas API (ya sea inicio de sesión inicial o actualización), el usuario puede cerrar sesión en el medio.
¿Cómo implementaría eso con thunks; mientras que también proporciona cobertura de prueba completa para todo el flujo? Así es como puede verse con Sagas:
En el ejemplo anterior, estamos expresando nuestro requisito de concurrencia usando
race
. Sitake(LOGOUT)
gana la carrera (es decir, el usuario hizo clic en el botón Cerrar sesión). La carrera cancelará automáticamente laauthAndRefreshTokenOnExpiry
tarea en segundo plano. Y siauthAndRefreshTokenOnExpiry
se bloqueó en medio de unacall(authorize, {token})
llamada, también se cancelará. La cancelación se propaga hacia abajo automáticamente.Puede encontrar una demostración ejecutable del flujo anterior
fuente
delay
función? Ah, lo encontré: github.com/yelouafi/redux-saga/blob/…redux-thunk
código es bastante legible y se explica por sí mismo. Peroredux-sagas
uno es realmente ilegible, principalmente a causa de los verbo-como funciones:call
,fork
,take
,put
...Agregaré mi experiencia usando saga en el sistema de producción, además de la respuesta bastante detallada del autor de la biblioteca.
Pro (usando saga):
Testabilidad Es muy fácil probar sagas ya que call () devuelve un objeto puro. La prueba de thunks normalmente requiere que incluyas un mockStore dentro de tu prueba.
redux-saga viene con muchas funciones útiles de ayuda sobre tareas. Me parece que el concepto de saga es crear algún tipo de trabajador / hilo de fondo para su aplicación, que actúe como una pieza faltante en la arquitectura react redux (actionCreators and reducers deben ser funciones puras). Lo que lleva al siguiente punto.
Las sagas ofrecen un lugar independiente para manejar todos los efectos secundarios. Por lo general, en mi experiencia, es más fácil modificar y administrar que las acciones thunk.
Estafa:
Sintaxis del generador.
Muchos conceptos para aprender.
Estabilidad API. Parece que redux-saga todavía está agregando características (¿canales?) Y la comunidad no es tan grande. Existe una preocupación si la biblioteca realiza una actualización no compatible con versiones anteriores algún día.
fuente
API stability
una actualización para reflejar la situación actual.Solo me gustaría agregar algunos comentarios de mi experiencia personal (usando tanto sagas como thunk):
Las sagas son geniales para probar:
Las sagas son más poderosas. Todo lo que puedes hacer en el creador de acción de un thunk también puedes hacerlo en una saga, pero no al revés (o al menos no fácilmente). Por ejemplo:
take
)cancel
,takeLatest
,race
)take
,takeEvery
, ...)Sagas también ofrece otra funcionalidad útil, que generaliza algunos patrones de aplicación comunes:
channels
para escuchar en fuentes de eventos externas (por ejemplo, websockets)fork
,spawn
)Las sagas son una gran y poderosa herramienta. Sin embargo, con el poder viene la responsabilidad. Cuando su aplicación crece, puede perderse fácilmente al descubrir quién está esperando que se envíe la acción o qué sucede cuando se envía alguna acción. Por otro lado, thunk es más simple y más fácil de razonar. Elegir uno u otro depende de muchos aspectos, como el tipo y el tamaño del proyecto, qué tipos de efectos secundarios debe manejar su proyecto o las preferencias del equipo de desarrollo. En cualquier caso, simplemente mantenga su aplicación simple y predecible.
fuente
Solo alguna experiencia personal:
Para codificar el estilo y la legibilidad, una de las ventajas más significativas de usar redux-saga en el pasado es evitar el infierno de devolución de llamada en redux-thunk: ya no es necesario usar muchos anidamientos / capturas. Pero ahora, con la popularidad de async / await thunk, también se podría escribir código asíncrono en estilo de sincronización cuando se utiliza redux-thunk, lo que puede considerarse como una mejora en redux-think.
Es posible que sea necesario escribir mucho más código repetitivo cuando se usa redux-saga, especialmente en Typecript. Por ejemplo, si se quiere implementar una función de recuperación asíncrona, el manejo de datos y errores se puede realizar directamente en una unidad thunk en action.js con una sola acción FETCH. Pero en redux-saga, uno puede necesitar definir las acciones FETCH_START, FETCH_SUCCESS y FETCH_FAILURE y todas sus verificaciones de tipo relacionadas, porque una de las características en redux-saga es usar este tipo de mecanismo rico de "token" para crear efectos e instruir tienda redux para pruebas fáciles. Por supuesto, uno podría escribir una saga sin usar estas acciones, pero eso lo haría similar a un thunk.
En términos de estructura de archivos, redux-saga parece ser más explícito en muchos casos. Uno podría encontrar fácilmente un código asíncrono relacionado en cada sagas.ts, pero en redux-thunk, uno debería verlo en acciones.
Las pruebas fáciles pueden ser otra característica ponderada en redux-saga. Esto es realmente conveniente. Pero una cosa que debe aclararse es que la prueba de "llamada" de redux-saga no realizaría una llamada API real en la prueba, por lo tanto, sería necesario especificar el resultado de la muestra para los pasos que pueden usar después de la llamada API. Por lo tanto, antes de escribir en redux-saga, sería mejor planificar una saga y sus correspondientes sagas.spec.ts en detalle.
Redux-saga también proporciona muchas características avanzadas, como ejecutar tareas en paralelo, ayudantes de concurrencia como takeLatest / takeEvery, fork / spawn, que son mucho más potentes que los thunks.
En conclusión, personalmente, me gustaría decir: en muchos casos normales y aplicaciones de tamaño pequeño a mediano, vaya con estilo asíncrono / espera estilo redux-thunk. Le ahorraría muchos códigos / acciones / typedefs repetitivos, y no necesitaría cambiar muchos sagas.ts diferentes y mantener un árbol de sagas específico. Pero si está desarrollando una aplicación grande con una lógica asincrónica muy compleja y la necesidad de características como la simultaneidad / patrón paralelo, o si tiene una gran demanda de pruebas y mantenimiento (especialmente en el desarrollo basado en pruebas), redux-sagas posiblemente podría salvarle la vida .
De todos modos, redux-saga no es más difícil y complejo que el redux en sí mismo, y no tiene la llamada curva de aprendizaje empinada porque tiene conceptos básicos y API bien limitados. Pasar una pequeña cantidad de tiempo aprendiendo redux-saga puede beneficiarse algún día en el futuro.
fuente
Habiendo revisado algunos proyectos diferentes de React / Redux a gran escala en mi experiencia, Sagas proporciona a los desarrolladores una forma más estructurada de escribir código que es mucho más fácil de probar y más difícil de equivocarse.
Sí, es un poco extraño para empezar, pero la mayoría de los desarrolladores lo entienden lo suficiente en un día. Siempre le digo a la gente que no se preocupe por lo
yield
que debe comenzar y que, una vez que escriba un par de pruebas, le llegará.He visto un par de proyectos en los que los thunks han sido tratados como si fueran controladores de la plataforma MVC y esto rápidamente se convierte en un desastre indestructible.
Mi consejo es usar Sagas donde necesites A desencadena cosas de tipo B relacionadas con un solo evento. Para cualquier cosa que pueda atravesar una serie de acciones, considero que es más simple escribir middleware para clientes y usar la metapropiedad de una acción de FSA para activarlo.
fuente
Thunks versus Sagas
Redux-Thunk
yRedux-Saga
difieren en algunas formas importantes, ambas son bibliotecas de middleware para Redux (el middleware de Redux es un código que intercepta las acciones que ingresan a la tienda a través del método dispatch ()).Una acción puede ser literalmente cualquier cosa, pero si sigue las mejores prácticas, una acción es un objeto javascript simple con un campo de tipo y campos opcionales de carga útil, meta y error. p.ej
Redux-Thunk
Además de despachar acciones estándar, el
Redux-Thunk
middleware le permite despachar funciones especiales, llamadasthunks
.Thunks (en Redux) generalmente tienen la siguiente estructura:
Es decir, a
thunk
es una función que (opcionalmente) toma algunos parámetros y devuelve otra función. La función interna toma unadispatch function
y unagetState
función, las cuales serán proporcionadas por elRedux-Thunk
middleware.Redux-Saga
Redux-Saga
El middleware le permite expresar una lógica de aplicación compleja como funciones puras llamadas sagas. Las funciones puras son deseables desde el punto de vista de la prueba porque son predecibles y repetibles, lo que las hace relativamente fáciles de probar.Las sagas se implementan a través de funciones especiales llamadas funciones generadoras. Estas son una nueva característica de
ES6 JavaScript
. Básicamente, la ejecución salta dentro y fuera de un generador donde sea que vea una declaración de rendimiento. Piense en unayield
declaración que hace que el generador haga una pausa y devuelva el valor producido. Más tarde, la persona que llama puede reanudar el generador en la declaración que sigue ayield
.Una función generadora se define así. Observe el asterisco después de la palabra clave de función.
Una vez que la saga de inicio de sesión esté registrada en
Redux-Saga
. Pero luego, layield
toma de la primera línea detendrá la saga hasta que se envíe una acción con tipo'LOGIN_REQUEST'
a la tienda. Una vez que eso suceda, la ejecución continuará.Para más detalles ver este artículo .
fuente
Una nota rápida Los generadores son cancelables, asíncronos / aguardan, no. Entonces, para un ejemplo de la pregunta, realmente no tiene sentido qué elegir. Pero para flujos más complicados a veces no hay mejor solución que usar generadores.
Entonces, otra idea podría ser usar generadores con redux-thunk, pero para mí, parece que trata de inventar una bicicleta con ruedas cuadradas.
Y, por supuesto, los generadores son más fáciles de probar.
fuente
Aquí hay un proyecto que combina las mejores partes (pros) de ambos
redux-saga
yredux-thunk
: puede manejar todos los efectos secundarios en las sagas mientras obtiene una promesa pordispatching
la acción correspondiente: https://github.com/diegohaz/redux-saga-thunkfuente
then()
dentro de un componente Reaccionar va en contra del paradigma. Debe manejar el estado modificado encomponentDidUpdate
lugar de esperar a que se resuelva una promesa.componentDidlMount() { this.props.doSomething().then((detail) => { this.setState({isReady: true})} }
Una forma más fácil es usar redux-auto .
de la documentación
La idea es tener cada acción en un archivo específico . co-ubicar la llamada al servidor en el archivo con funciones reductoras para "pendiente", "cumplida" y "rechazada". Esto hace que el manejo de las promesas sea muy fácil.
También adjunta automáticamente un objeto auxiliar (llamado "asíncrono") al prototipo de su estado, lo que le permite rastrear en su UI las transiciones solicitadas.
fuente