https://github.com/redux-saga/redux-saga
英文官网:https://redux-saga.js.org/
中文翻译:https://redux-saga-in-chinese.js.org/
简述
- redux-saga 是一个redux的中间件,而中间件的作用是为 redux 提供额外的功能。
- 在 reducers 中的所有操作都是同步的并且是纯粹的,即 reducer 都是纯函数,纯函数是指一个函数的返回结果只依赖于它的参数,并且在执行过程中不会对外部产生副作用。
- 但是在实际的应用开发中,我们希望做一个异步的(如Ajax请求)且不纯粹的操作(如改变外部的状态),这些在函数式编程范式中被称为“副作用”。
redux-saga 就是用来处理上述副作用(异步任务)的一个中间件。它是一个接收事件,并可能触发新事件的过程管理者,为你的应用管理复杂的流程。
redux-saga 工作原理
- sagas采用Generator函数来 yield Effects(包含指令的文本对象)。
- Generator 函数的作用是可以暂停执行,再次执行的时候从上次暂停的地方继续执行。
- Effect 是一个简单的对象,该对象包含了一些给 middleware 解释执行的信息。
- 你可以通过使用 effects API 如:fork、call、take、put、cancel等来创建 Effect。
redux-saga 分类
- worker saga 做左右的工作,如调用API,进行异步请求,获取异步封装的结果。
- watcher saga 监听被dispatch的actions,当接受到actions或者知道其被触发时,调用worker执行任务。
- root saga 立即启动saga的唯一入口。

Generator 回顾
https://www.yuque.com/zhuchaoyang/wrif6k/ozcfr0
// 生成器function* gen(){console.log('start');let a = yield 1;console.log('a=>', a);let b = 10 * (yield 2);console.log('b=>', b);let c = yield 3;console.log('c=>', c);}// 生成器执行返回一个迭代器let it = gen();console.log(it.next());// start// {value: 1, done: false} value等于yield后面的那个表示式的值console.log(it.next());// a=> undefined// {value: 2, done: false}console.log(it.next(4)); //参数4等于上一个yield表达式的值,即 b = 10 * 4// b=> 40// {value: 3, done: false}console.log(it.next());// {value: undefined, done: true}
take、put
take监听某个动作的发生。如果有人派发了这个动作,当前saga就继续执行;如果没有,就阻塞在这里。put派发一个真正的动作。
src/index.js
import React from 'react';import ReactDOM from 'react-dom';import {Provider} from 'react-redux';import store from './store';import Counter from './components/Counter';function App(){return (<Provider store={store}><Counter /></Provider>)}ReactDOM.render(<App />, document.querySelector('#root'));
src/store/index.js
import {createStore, applyMiddleware} from 'redux';import createSagaMiddleware from 'redux-saga';import reducers from './reducers';import rootSaga from './sagas';let sagaMiddleware = createSagaMiddleware(); //创建saga中间件let store = applyMiddleware(sagaMiddleware)(createStore)(reducers);sagaMiddleware.run(rootSaga); //启动rootSagaexport default store;
src/store/action-types.js
export const ASYNC_ADD = 'ASYNC_ADD'; //异步加1,发送给监听sagaexport const ADD = 'ADD'; //同步加1,发送给worker sagaexport const ASYNC_MINUS = 'ASYNC_MINUS'; //异步减1export const MINUS = 'MINUS'; //同步减1
src/store/sagas
src/store/sagas/index.js
import {take, put} from 'redux-saga/effects';import * as actionTypes from '../action-types';// 根sagaexport default function* rootSaga(){console.log('rootSaga开始执行');for (let i=0; i<3; i++){ //只会执行3次,3次之后,再点就没有效果了// for (;;){ //如果不想这样,可以写一个死循环,每次点都是有效果的。// 在此监听某个动作的发生,// 如果有人派发这个动作,当前saga就继续执行;如果没有,就暂停阻塞在这里。let aa = yield take(actionTypes.ASYNC_ADD);console.log('此处模拟一个延迟 1 秒', aa); //{type: "ASYNC_ADD"}// 派发一个真正的动作,即 store.dispatch({type: actionTypes.ADD})let bb = yield put({type: actionTypes.ADD});console.log(bb); //{type: "ADD", @@redux-saga/SAGA_ACTION: true}}console.log('rootSaga结束执行');}
src/store/actions
src/store/actions/counter.js
import * as actionTypes from '../action-types';const actions = {add(){return {type: actionTypes.ADD};},asyncAdd(){return {type: actionTypes.ASYNC_ADD, payload: {step: 2}};},minus(){return {type: actionTypes.MINUS};},asyncMinus(){return {type: actionTypes.ASYNC_MINUS};},}export default actions;
src/store/reducers
src/store/reducers/index.js
import * as actionTypes from '../action-types';function reducer(state={number:0}, action){switch(action.type){case actionTypes.ADD:return {number: state.number+1};case actionTypes.MINUS:return {number: state.number-1};default:return state;}}export default reducer;
src/components/Counter.js
import React from 'react';import {connect} from 'react-redux';import actions from '../store/actions/counter.js';class Counter extends React.Component {render(){let ps = this.props;return (<div><p>{ps.number}</p><div><button onClick={ps.add}>同步+</button><button onClick={ps.asyncAdd}>异步+</button></div><div><button onClick={ps.minus}>同步-</button><button onClick={ps.asyncMinus}>异步-</button></div></div>)}}export default connect(state => state, actions)(Counter)
产出 iterator
src/store/sagas/index.js
import {take, put} from 'redux-saga/effects';import * as actionTypes from '../action-types';+ // work saga+ function* add(){+ yield put({type: actionTypes.ADD});+ }// 根sagaexport default function* rootSaga(){console.log('rootSaga开始执行');for (let i=0; i<3; i++){yield take(actionTypes.ASYNC_ADD);- // yield put({type: actionTypes.ADD});+ yield add(); //产出一个迭代器}console.log('rootSaga结束执行');}
takeEvery
- 一个task就像是一个在后台运行的进程,在基于redux-saga的应用程序中,可以同时运行多个task
- 通过fork函数来创建task
拆分前 src/store/sagas/index.js
```javascript
import {take, put, takeEvery} from ‘redux-saga/effects’;
import * as actionTypes from ‘../action-types’;
// work saga function* addWorker(){ yield put({type: actionTypes.ADD}); }
// 根saga export default function* rootSaga(){ console.log(‘rootSaga开始执行’); // 监听每一次的ASYNC_ADD动作,等到后执行add, takeEvery并不会阻塞当前的saga。 let aa = yield takeEvery(actionTypes.ASYNC_ADD, addWorker); console.log(‘rootSaga结束执行’, aa); //{@@redux-saga/TASK: true, …} }
<a name="gllq5"></a>## 拆分后 src/store/sagas/index.js```javascriptimport addListener from './add';// 根sagaexport default function* rootSaga(){yield addListener();}
拆分后 src/store/sagas/add.js
import {put, takeEvery} from '../../redux-saga/effects';import * as actionTypes from '../action-types';// work sagafunction* addWorker(action){console.log('action=>', action); //action=> {type: "ASYNC_ADD", payload: {step: 2}}yield put({type: actionTypes.ADD});}// 监听sagafunction* addListener(){yield takeEvery(actionTypes.ASYNC_ADD, addWorker);}export default addListener;
支持Promise
src/store/sagas/delay.js
// 模拟延迟export const delay = (ms=1000, val=true) => new Promise((resolve, reject) => {setTimeout(resolve, ms, val);});// 模拟异步返回数据export const delayMock = (data, ms) => delay(ms, {result: 1,data,})
src/store/sagas/add.js
import {put, takeEvery} from 'redux-saga/effects';import * as actionTypes from '../action-types';+ import {delayMock} from './delay';// work sagafunction* addWorker(action){+ const data = yield delayMock({id: 1, name: 'jack'});+ console.log('异步返回数据=>', data); //异步返回数据=> {result: 1, data: {id: 1, name: "jack"}}yield put({type: actionTypes.ADD});}// 监听sagafunction* addListener(){yield takeEvery(actionTypes.ASYNC_ADD, addWorker);}export default addListener;
call 处理一个 返回值为Promise的函数
- import {put, takeEvery, call} from ‘redux-saga/effects’; import * as actionTypes from ‘../action-types’; import {delayMock} from ‘./delay’;
// work saga function* addWorker(action){
- const data = yield call(delayMock, {id: 1, name: ‘jack’}); console.log(‘异步返回数据=>’, data); //异步返回数据=> {result: 1, data: {id: 1, name: “jack”}} yield put({type: actionTypes.ADD}); }
// 监听saga function* addListener(){ yield takeEvery(actionTypes.ASYNC_ADD, addWorker); }
export default addListener;
<a name="UIyvv"></a># cps 处理一个 回调形式的函数- `yield cps(fn, arg1, arg2, ...)`<a name="LI1p7"></a>## src/store/sagas/add.js```javascriptimport {put, takeEvery, call, cps} from 'redux-saga/effects';import * as actionTypes from '../action-types';import {delayMock} from './delay';// 模拟回调函数形式const delayCallback = (ms, callback) => {setTimeout(() => {let number = Math.random();if (number > 0.5){callback(null, number);} else {callback('太小了', number);}}, ms)}// work sagafunction* addWorker(action){//const data = yield call(delayMock, {id: 1, name: 'jack'});const data = yield cps(delayCallback, 1000)console.log('异步返回数据=>', data); //异步返回数据=> 0.536148468128234yield put({type: actionTypes.ADD});}// 监听sagafunction* addListener(){yield takeEvery(actionTypes.ASYNC_ADD, addWorker);}export default addListener;
all
如果有多个监听saga,用 all 实现类似于Promise.all的效果。
src/store/sagas/index.js
import {all} from '../../redux-saga/effects';import addListener from './add';import minusListener from './minus';// 根sagaexport default function* rootSaga(){// yield addListener();// yield minusListener();// 如果有多个监听saga,用 all 实现类似于Promise.all的效果。// 上图示例如下:必须等2个监听saga都完成了(3个+,2个- 动作),根rootSagac才继续执行。// 如果有任意监听saga未完成,就会先等着。let result = yield all([addListener(),minusListener(),]);console.log('result=>', result);}
src/store/sagas/add.js
import {put, take, takeEvery, call} from '../../redux-saga/effects';import * as actionTypes from '../action-types';import {delayMock} from './delay';// work sagafunction* addWorker(action){const data = yield call(delayMock, {id: 1, name: 'jack'});yield put({type: actionTypes.ADD});}// 监听sagafunction* addListener(){// yield takeEvery(actionTypes.ASYNC_ADD, addWorker);for (let i=0; i<3; i++){yield take(actionTypes.ASYNC_ADD);yield put({type: actionTypes.ADD});}return 'add3';}export default addListener;
src/store/sagas/minus.js
import {put, take, takeEvery, call} from '../../redux-saga/effects';import * as actionTypes from '../action-types';import {delayMock} from './delay';// work sagafunction* minusWorker(action){const data = yield call(delayMock);yield put({type: actionTypes.MINUS});}// 监听sagafunction* minusListener(){// yield takeEvery(actionTypes.ASYNC_MINUS, minusWorker);for (let i=0; i<2; i++){yield take(actionTypes.ASYNC_MINUS);yield put({type: actionTypes.MINUS});}return 'minus2';}export default minusListener;
cancel 取消saga任务
src/store/sagas/index.js
import {all, fork, put, take, delay, cancel} from '../../redux-saga/effects';import * as actionTypes from '../action-types';// 每隔1秒加1function* add(){while(true){yield delay(1000);yield put({type: actionTypes.ADD});}}function* addListener(){// 开启一个新的子进程去执行add,返回add的task任务对象const task = yield fork(add);// 等待有人向仓库派发 STOP 这个动作,如果有人派发了,就继续向下执行yield take(actionTypes.STOP);// 取消 task 任务执行(cancel不会阻塞,它执行后,会继续向下走)yield cancel(task);console.log('父saga继续向前执行');}// 根sagaexport default function* rootSaga(){yield addListener();}
src/components/Counter.js
import React from 'react';import {connect} from 'react-redux';import actions from '../store/actions/counter.js';class Counter extends React.Component {render(){let ps = this.props;return (<div><p>{ps.number}</p><div><button onClick={ps.stop}>停止</button></div></div>)}}export default connect(state => state, actions)(Counter)
