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); //启动rootSaga
export default store;
src/store/action-types.js
export const ASYNC_ADD = 'ASYNC_ADD'; //异步加1,发送给监听saga
export const ADD = 'ADD'; //同步加1,发送给worker saga
export const ASYNC_MINUS = 'ASYNC_MINUS'; //异步减1
export 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';
// 根saga
export 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});
+ }
// 根saga
export 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
```javascript
import addListener from './add';
// 根saga
export default function* rootSaga(){
yield addListener();
}
拆分后 src/store/sagas/add.js
import {put, takeEvery} from '../../redux-saga/effects';
import * as actionTypes from '../action-types';
// work saga
function* addWorker(action){
console.log('action=>', action); //action=> {type: "ASYNC_ADD", payload: {step: 2}}
yield put({type: actionTypes.ADD});
}
// 监听saga
function* 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 saga
function* 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});
}
// 监听saga
function* 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
```javascript
import {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 saga
function* addWorker(action){
//const data = yield call(delayMock, {id: 1, name: 'jack'});
const data = yield cps(delayCallback, 1000)
console.log('异步返回数据=>', data); //异步返回数据=> 0.536148468128234
yield put({type: actionTypes.ADD});
}
// 监听saga
function* 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';
// 根saga
export 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 saga
function* addWorker(action){
const data = yield call(delayMock, {id: 1, name: 'jack'});
yield put({type: actionTypes.ADD});
}
// 监听saga
function* 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 saga
function* minusWorker(action){
const data = yield call(delayMock);
yield put({type: actionTypes.MINUS});
}
// 监听saga
function* 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秒加1
function* 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继续向前执行');
}
// 根saga
export 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)