Se habla mucho sobre el último niño en redux town en este momento, redux-saga / redux-saga . Utiliza funciones generadoras para escuchar / enviar acciones.
Antes de comprenderlo, me gustaría saber los pros / contras de usar en redux-saga
lugar del enfoque a continuación donde estoy usando redux-thunk
async / await.
Un componente podría tener este aspecto, distribuir 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 ven así:
// 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...
En redux-saga, el equivalente del ejemplo anterior sería
export function* loginSaga() {
while(true) {
const { user, pass } = yield take(LOGIN_REQUEST)
try {
let { data } = yield call(request.post, '/login', { user, pass });
yield fork(loadUserData, data.uid);
yield put({ type: LOGIN_SUCCESS, data });
} catch(error) {
yield put({ type: LOGIN_ERROR, error });
}
}
}
export function* loadUserData(uid) {
try {
yield put({ type: USERDATA_REQUEST });
let { data } = yield call(request.get, `/users/${uid}`);
yield put({ type: USERDATA_SUCCESS, data });
} catch(error) {
yield put({ type: USERDATA_ERROR, error });
}
}
Lo primero que debemos notar es que estamos llamando a las funciones 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 usando simples verificaciones de igualdad
const iterator = loginSaga()
assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))
// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
iterator.next(mockAction).value,
call(request.post, '/login', mockAction)
)
// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
iterator.throw(mockError).value,
put({ type: LOGIN_ERROR, error: mockError })
)
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. La burla de datos es mucho más simple que las funciones de burla.
La segunda cosa a tener en cuenta es la llamada a yield take(ACTION)
. El creador de la acción llama a los thunks en cada nueva acción (p LOGIN_REQUEST
. Ej .). 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, los generadores tiran 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, lo que de alguna manera imita el comportamiento de empuje del procesador.
El enfoque pull permite implementar flujos de control complejos. Supongamos, por ejemplo, que queremos agregar los siguientes requisitos
Manejar la acción del usuario LOGOUT
en el primer inicio de sesión exitoso, el servidor devuelve un token que caduca con cierto retraso almacenado en un expires_in
campo. Tendremos que actualizar la autorización en segundo plano cada expires_in
milisegundos
Tenga en cuenta que cuando espera el resultado de las llamadas a la API (ya sea inicio de sesión inicial o actualización), el usuario puede cerrar sesión en el medio.
¿Cómo implementarías eso con thunks? al mismo tiempo que proporciona una cobertura de prueba completa para todo el flujo? Así es como puede verse con Sagas:
function* authorize(credentials) {
const token = yield call(api.authorize, credentials)
yield put( login.success(token) )
return token
}
function* authAndRefreshTokenOnExpiry(name, password) {
let token = yield call(authorize, {name, password})
while(true) {
yield call(delay, token.expires_in)
token = yield call(authorize, {token})
}
}
function* watchAuth() {
while(true) {
try {
const {name, password} = yield take(LOGIN_REQUEST)
yield race([
take(LOGOUT),
call(authAndRefreshTokenOnExpiry, name, password)
])
// user logged out, next while iteration will wait for the
// next LOGIN_REQUEST action
} catch(error) {
yield put( login.error(error) )
}
}
}
En el ejemplo anterior, expresamos nuestro requisito de concurrencia mediante race
. Si take(LOGOUT)
gana la carrera (es decir, el usuario hizo clic en un botón Cerrar sesión). La carrera cancelará automáticamente la authAndRefreshTokenOnExpiry
tarea en segundo plano. Y si authAndRefreshTokenOnExpiry
se bloqueó en medio de una call(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
Agregaré mi experiencia usando saga en el sistema de producción además de la respuesta bastante completa del autor de la biblioteca.
Pro (usando saga):
Testabilidad. Es muy fácil probar sagas ya que call () devuelve un objeto puro. Probar los procesadores normalmente requiere que incluya un mockStore dentro de su prueba.
redux-saga viene con muchas funciones de ayuda útiles sobre tareas. Me parece que el concepto de saga es crear algún tipo de subproceso / trabajador en segundo plano para su aplicación, que actúa como una pieza faltante en la arquitectura react redux (actionCreators y redux 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, es más fácil de modificar y administrar que las acciones de procesador en mi experiencia.
Estafa:
Sintaxis del generador.
Muchos conceptos para aprender.
Estabilidad API. Parece que redux-saga todavía está agregando funciones (por ejemplo, ¿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.
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 lo puedes hacer 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 otras funciones útiles, que generalizan algunos patrones de aplicación comunes:
channels
para escuchar en fuentes de eventos externas (por ejemplo, websockets)fork
, spawn
)Las sagas son una herramienta excelente y poderosa. 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. La elección de uno u otro depende de muchos aspectos como el tipo y tamaño del proyecto, los tipos de efectos secundarios que debe manejar su proyecto o la preferencia del equipo de desarrollo. En cualquier caso, mantenga su aplicación simple y predecible.
Actualización en julio de 2020:
Durante los últimos 16 meses, quizás el cambio más notable en la comunidad de React son los ganchos de React .
Según lo que observo, para lograr una mejor compatibilidad con componentes funcionales y ganchos, los proyectos (incluso los grandes) tenderían a usar:
useQuery
useMutation
En comparación, redux-saga
realmente no proporciona un beneficio significativo en la mayoría de los casos normales de llamadas a la API en comparación con los enfoques anteriores por ahora, mientras que aumenta la complejidad del proyecto al introducir muchos archivos / generadores de saga (también porque la última versión v1.1.1 de redux-saga
fue el 18 de septiembre 2019, que fue hace mucho tiempo).
Pero aún así, redux-saga
proporciona algunas características únicas, como el efecto de carrera y las solicitudes paralelas. Por tanto, si necesita estas funcionalidades especiales, redux-saga
sigue siendo una buena elección.
Publicación original en marzo de 2019:
Solo una experiencia personal:
Para el estilo de codificación 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 then / catch. Pero ahora, con la popularidad de async / await thunk, también se podría escribir código async en estilo sync cuando se usa redux-thunk, lo que puede considerarse como una mejora en redux-thunk.
Es posible que deba escribir muchos más códigos repetitivos cuando se usa redux-saga, especialmente en Typecript. Por ejemplo, si uno quiere implementar una función de recuperación asíncrona, los datos y el manejo de errores podrían realizarse directamente en una unidad de procesador en action.js con una sola acción FETCH. Pero en redux-saga, es posible que deba definir las acciones FETCH_START, FETCH_SUCCESS y FETCH_FAILURE y todas sus verificaciones de tipo relacionadas, porque una de las características de redux-saga es utilizar este tipo de mecanismo rico de "token" para crear efectos e instruir tienda redux para una prueba fácil. Por supuesto, uno podría escribir una saga sin usar estas acciones, pero eso la haría similar a un thunk.
En términos de la estructura del archivo, redux-saga parece ser más explícito en muchos casos. Uno podría encontrar fácilmente un código relacionado con async en cada sagas.ts, pero en redux-thunk, uno necesitarí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 a la API real en la prueba, por lo que se necesitaría especificar el resultado de muestra para los pasos que se pueden usar después de la llamada a la API. Por 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 poderosos que los thunks.
En conclusión, personalmente, me gustaría decir: en muchos casos normales y aplicaciones de tamaño pequeño a mediano, elija el estilo async / await redux-thunk. Le ahorraría muchos códigos / acciones / typedefs estándar, y no necesitaría cambiar entre 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 concurrencia / patrón paralelo, o tiene una gran demanda de pruebas y mantenimiento (especialmente en el desarrollo impulsado por pruebas), redux-sagas posiblemente le salvaría la vida .
De todos modos, redux-saga no es más difícil y complejo que el propio redux, 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 beneficiarte algún día en el futuro.
Habiendo revisado algunos proyectos 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 comprenden lo suficiente en un día. Siempre le digo a la gente que no se preocupe por lo que yield
hace para empezar y que una vez que escribas un par de pruebas te llegará.
He visto un par de proyectos en los que los procesadores han sido tratados como si fueran controladores del patrón MVC y esto se convierte rápidamente en un desastre imposible de editar.
Mi consejo es que uses Sagas donde necesites que A desencadena cosas de tipo B relacionadas con un solo evento. Para cualquier cosa que pueda atravesar una serie de acciones, creo que es más sencillo escribir middleware del cliente y usar la propiedad meta de una acción FSA para activarla.
Thunks versus Sagas
Redux-Thunk
y Redux-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 llegan 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 simple de JavaScript con un campo de tipo y campos de error, metadatos y carga útil opcionales. p.ej
const loginRequest = {
type: 'LOGIN_REQUEST',
payload: {
name: 'admin',
password: '123',
}, };
Redux-Thunk
Además de distribuir acciones estándar, el Redux-Thunk
middleware le permite distribuir funciones especiales, llamadas thunks
.
Los procesadores (en Redux) generalmente tienen la siguiente estructura:
export const thunkName =
parameters =>
(dispatch, getState) => {
// Your application logic goes here
};
Es decir, a thunk
es una función que (opcionalmente) toma algunos parámetros y devuelve otra función. La función interna toma una función dispatch function
y una getState
, las cuales serán proporcionadas por el Redux-Thunk
middleware.
Redux-Saga
Redux-Saga
El middleware le permite expresar la lógica de aplicaciones complejas como funciones puras llamadas sagas. Las funciones puras son deseables desde el punto de vista de las pruebas 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. Éstas son una nueva característica de ES6 JavaScript
. Básicamente, la ejecución entra y sale de un generador en cualquier lugar donde vea una declaración de rendimiento. Piense en una yield
declaración como que hace que el generador se detenga y devuelva el valor obtenido. Más tarde, la persona que llama puede reanudar el generador en la declaración que sigue al yield
.
Una función generadora es una definida así. Observe el asterisco después de la palabra clave de función.
function* mySaga() {
// ...
}
Una vez que la saga de inicio de sesión está registrada con Redux-Saga
. Pero luego la yield
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á.
Una nota rápida. Los generadores son cancelables, asincrónicos / en espera, 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 utilizar generadores.
Entonces, otra idea podría ser usar generadores con redux-thunk, pero a mí me parece como intentar inventar una bicicleta con ruedas cuadradas.
Y, por supuesto, los generadores son más fáciles de probar.
Aquí hay un proyecto que combina las mejores partes (pros) de ambos redux-saga
y redux-thunk
: puedes manejar todos los efectos secundarios de las sagas mientras obtienes una promesa con dispatching
la acción correspondiente:
https://github.com/diegohaz/redux-saga-thunk
class MyComponent extends React.Component {
componentWillMount() {
// `doSomething` dispatches an action which is handled by some saga
this.props.doSomething().then((detail) => {
console.log('Yaay!', detail)
}).catch((error) => {
console.log('Oops!', error)
})
}
}
Una forma más sencilla es utilizar redux-auto .
de la documantasion
redux-auto solucionó este problema asincrónico simplemente permitiéndole crear una función de "acción" que devuelve una promesa. Para acompañar su lógica de acción de función "predeterminada".
La idea es tener cada acción en un archivo específico . colocando la llamada al servidor en el archivo con funciones reductoras para "pendiente", "cumplida" y "rechazada". Esto hace que manejar las promesas sea muy fácil.
También adjunta automáticamente un objeto auxiliar (llamado "async") al prototipo de su estado, lo que le permite realizar un seguimiento en su interfaz de usuario, las transiciones solicitadas.
La estrella de HGTV, Christina Hall, revela que le diagnosticaron envenenamiento por mercurio y plomo, probablemente debido a su trabajo como manipuladora de casas.
Recientemente salió a la luz un informe policial que acusa a la estrella de 'Love Is Blind', Brennon, de violencia doméstica. Ahora, Brennon ha respondido a los reclamos.
Conozca cómo Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia mientras organizaba la primera celebración de Acción de Gracias desde que murió su madre, Naomi Judd.
Descubra por qué un destacado experto en lenguaje corporal cree que es fácil trazar "tales paralelismos" entre la princesa Kate Middleton y la princesa Diana.
Los inodoros arrojan columnas de aerosol invisibles con cada descarga. ¿Como sabemos? La prueba fue capturada por láseres de alta potencia.
Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?
The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!
¿Sigue siendo efectivo ese lote de repelente de insectos que te quedó del verano pasado? Si es así, ¿por cuánto tiempo?
Tapas elásticas de silicona de Tomorrow's Kitchen, paquete de 12 | $14 | Amazonas | Código promocional 20OFFKINJALids son básicamente los calcetines de la cocina; siempre perdiéndose, dejando contenedores huérfanos que nunca podrán volver a cerrarse. Pero, ¿y si sus tapas pudieran estirarse y adaptarse a todos los recipientes, ollas, sartenes e incluso frutas en rodajas grandes que sobran? Nunca más tendrás que preocuparte por perder esa tapa tan específica.
Hemos pirateado algunas ciudades industriales en esta columna, como Los Ángeles y Las Vegas. Ahora es el momento de una ciudad militar-industrial-compleja.
Un minorista está enlatando su sección de tallas grandes. Pero no están tomando la categoría solo en línea o descontinuándola por completo.
Entiendo totalmente, completamente si tienes una relación difícil con los animales de peluche. Son lindos, tienen valor sentimental y es difícil separarse de ellos.
El equipo está a la espera de las medallas que ganó en los Juegos Olímpicos de Invierno de 2022 en Beijing, ya que se está resolviendo un caso de dopaje que involucra a la patinadora artística rusa Kamila Valieva.
Miles de compradores de Amazon recomiendan la funda de almohada de seda Mulberry, y está a la venta en este momento. La funda de almohada de seda viene en varios colores y ayuda a mantener el cabello suave y la piel clara. Compre las fundas de almohada de seda mientras tienen hasta un 46 por ciento de descuento en Amazon
El jueves se presentó una denuncia de delito menor amenazante agravado contra Joe Mixon.
El Departamento de Policía de Lafayette comenzó a investigar a un profesor de la Universidad de Purdue en diciembre después de recibir varias denuncias de un "hombre sospechoso que se acercaba a una mujer".
Al igual que el mundo que nos rodea, el lenguaje siempre está cambiando. Mientras que en eras anteriores los cambios en el idioma ocurrían durante años o incluso décadas, ahora pueden ocurrir en cuestión de días o incluso horas.
Estoy de vuelta por primera vez en seis años. No puedo decirte cuánto tiempo he estado esperando esto.
“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.
Creo, un poco tarde en la vida, en dar oportunidades a la gente. Generosamente.