redux-saga
es una librería hecha con la intención de facilitar el manejo de efectos secundarios (side effects) (ej. operaciones asíncronas como la obtención de datos (data fetching) y cosas impuras como el acceso al cache del navegador), de manera más eficiente, más simple de probar, y para mejorar el manejo de fallas.
El modelo mental es que una saga represente (a manera de simulación) un hilo diferente en la aplicación y que únicamente sea responsable de los efectos secundarios. redux-saga
es un middleware (capa intermedia) de redux, lo que significa que este "hilo" puede ser iniciado, suspendido, y cancelado desde la aplicación principal con una acción cualquiera de redux, tiene acceso a todo el estado (state) de la aplicación en redux y también puede ejecutar (dispatch) acciones en redux.
Usa una de las caracteristicas de ES6 llamada Generadores (Generators) para que procedimientos o operaciones asíncronas sean fáciles de leer, escribir y probar. (Sí no estás familirizado con generadores aquí hay algúnos enlaces introductorios en inglés) Al usar estos generadores, estas opreaciones asíncronas se asemejan a código común y corriente síncrono en JavaScript. (Algo así como async
/await
, pero los generadores tienen algúnas ventajas adicionales excelentes que necesitamos)
Es posible que ya hayas usado redux-thunk
para manejar la obtención de datos en tu aplicación. Contrario a redux-thunk
, aquí no necesitarás llenar tu aplicación de callback hells, también podrás probar tus operaciones asíncronas de manera sencilla y tus acciones en redux se mantendrán puras(pure actions).
$ npm install --save redux-saga
o
$ yarn add redux-saga
También puedes usar los builds UMD que se encuentran directamente en el tag <script>
de una página HTML. Ve esta sección.
Imagina que tienes un UI que necesita solicitar datos de un servidor cuando un botón en pantalla es presionado. (Por simplicidad solo te mostraremos la acción que ejecutará el código)
class UserComponent extends React.Component {
...
onSomeButtonClicked() {
const { userId, dispatch } = this.props
dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
}
...
}
El Componente envía (dispatches) un objeto de una acción hacia el Store (almacén de datos). Crearemos una Saga que observe todas las acciones de tipo USER_FETCH_REQUESTED
y que ejecute una llamada de API para solicitar los datos del usuario.
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'
// Saga: será ejecutada cuando la acción USER_FETCH_REQUESTED sea envíada/ejecutada
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});
}
}
/*
Llama a fetchUser cada vez que la acción `USER_FETCH_REQUESTED` es enviada/ejecutada.
`takeEvery` permite que las peticiones se ejecuten de manera concurrente.
*/
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
/*
Igualmente podrías usar `takeLatest`.
No permite solicitudes concurrentes del mismo tipo de accion. Si una accion de tipo `USER_FETCH_REQUESTED`
se envía mientras otra está está siendo ejecutada en ese preciso momento, la acción que está siendo ejecutada será cancelada y sólo la última en recibirse será ejecutada.
*/
function* mySaga() {
yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}
export default mySaga;
Para ejecutar una Saga, necesitamos conectarla a un Redux Store (almacén de datos Redux) usando redux-saga
como un middleware.
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'
// creamos el saga middleware
const sagaMiddleware = createSagaMiddleware()
// lo montamos en la Redux Store
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
// y se inicia la saga
sagaMiddleware.run(mySaga)
// por último se muestra la aplicación
- Introducción
- Conceptos Básicos
- Conceptos avanzados
- Recetas (Recipes)
- Referencias externas
- Como solucionar problemas comúnes
- Golsario
- Documentación del API
Además de npm
y yarn
, existe una distribución umd de redux-saga
y se encuentra disponible en la carpeta dist/
. Cuando se utilice un build umd de redux-saga
este estará disponible como ReduxSaga
en el objeto window
. Esto permite que se pueda create un Saga middleware sin necesidad de usar un import
de ES6, esto funciona de la siguiente manera:
var sagaMiddleware = ReduxSaga.default()
La versión umd es útil si no se utiliza Webpack o Browserify. Se puede encontrar directamente en unpkg.
Los siguientes builds están disponibles:
- https://unpkg.com/redux-saga/dist/redux-saga.umd.js
- https://unpkg.com/redux-saga/dist/redux-saga.min.umd.js
¡Importante! Si el navegador que utilizara la aplicación no tiene soporte para generadores de ES2015, se deberá transpilarlos (ej. con el plugin babel) y proveer un runtime válido, como este. Este runtime debe ser importado antes de importar redux-saga:
import 'regenerator-runtime/runtime'
// después
import sagaMiddleware from 'redux-saga'
$ git clone https://github.com/redux-saga/redux-saga.git
$ cd redux-saga
$ yarn
$ npm test
Más abajo podrás encontrar ejemplos portados (hasta el momento) de los repos de Redux.
Hay tres ejemplos de contadores.
Demo usando vanilla JavaScript y builds UMD. Todo el código fuente está escrito directamente en index.html
.
Para ejecuta el ejemplo, solo abre index.html
en tu navegador.
Importante: tu navegador debe contar con soporte para generadores. Las últimas versiones de Chrome/Firefox/Edge tienen el soporte necesario.
Demo usando webpack
y el API de alto nivel takeEvery
.
$ npm run counter
# probar el generador de ejemplo
$ npm run test-counter
Demo usando un API de bajo nivel que demuestra como cancelar una tarea.
$ npm run cancellable-counter
$ npm run shop
# probar el generador de ejemplo
$ npm run test-shop
$ npm run async
# probar el ejemplo de generadores
$ npm run test-async
$ npm run real-world
# lo siento, aún no se implementan las pruebas
Redux-Saga con TypeScript requieren de DOM.Iterable
o ES2015.Iterable
. Si tu target
es ES6
, es posible que estés listo, sin embargo, si pleaneas ejecutarlo en una ambiente ES5
necesitarás añadirlo por tu cuenta.
Revisa tu archivo tsconfig.json
, y la documentación oficial para las opciones del compilador.
El logo oficial de Redux-Saga en diferentes estilos lo puedes encontrar en el directorio de logos.
Por favor, apoyanos con una donación mensual para seguir seguir con nuestras actividades. [Convertirme en backer]
Conviertete en patrocinador y pon tu logo en nuestro README en Github con un enlace a tu sitio. [Convertirme en patrocinador]