redux-saga
官网:redux-saga.js.org/docs/About
中文网:redux-saga-in-chinese.js.org/redux-saga 是一个用于管理
异步获取数据(副作用)的redux中间件;它的目标是让副作用管理更容易,执行更高效,测试更简单,处理故障时更容易…学习 redux-saga 之前,需要先掌握 ES6 中的 Iterator迭代器 和 Generator生成器 !!
1. redux-thunk与redux-saga的比较
redux中的数据流
action ———> reducer ———> state
- action是一个纯粹对象(plain object)
- reducer是一个纯函数(和外界没有任何关系)
- 都只能处理同步的操作
redux-thunk中间件的处理流程
action1 ———> middleware ———> action2 ———> reducer ———> state
/* redux-thunk中间件的部分源码 */
'use strict';
function createThunkMiddleware(extraArgument) {
var middleware = function middleware(_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
return function (action) {
if (typeof action === 'function') {
// 如果返回的action是个函数,则把函数执行「在函数中完成异步操作,用传递的dispatch单独实现派发!!」
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
return middleware;
}
弊端:异步操作分散到每一个action中;而且返回函数中的代码具备多样性!!
redux-saga中间件的工作流程
redux-saga中提供了一系列的api,可以去监听纯粹对象格式的action,方便单元测试!!
action1 ———> redux-saga监听 ———> 执行提供的API方法 ———> 返回描述对象 ———> 执行异步操作 ———> action2 ———> reducer ———> state
这样说完,大家可能是不太理解的,那么接下来,我们去做一个案例,详细解读一下redux-saga的语法和优势!!
2. redux-saga的基础知识
备注:需要先准备一套基于 “redux、redux-thunk” 实现的投票案例,我们在此基础上去修改 !!
安装中间件
$ npm install redux-saga
$ yarn add redux-saga
使用中间件
store/index.js
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
import saga from './saga';
// saga
const sagaMiddleware = createSagaMiddleware();
// 创建store容器
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
);
// 启动saga
sagaMiddleware.run(saga);
export default store;
store/action-types.js
export const VOTE_SUP = "VOTE_SUP";
export const VOTE_OPP = "VOTE_OPP";
export const DEMO = "DEMO";
store/reducer
// demoReducer
import * as TYPES from '../action-types';
import _ from '../../assets/utils';
let initial = {
num: 0
};
export default function demoReducer(state = initial, action) {
state = _.clone(state);
let { payload = 1 } = action;
switch (action.type) {
case TYPES.DEMO:
state.num += payload;
break;
default:
}
return state;
};
// index.js
import { combineReducers } from 'redux';
import voteReducer from './voteReducer';
import demoReducer from './demoReducer';
const reducer = combineReducers({
vote: voteReducer,
demo: demoReducer
});
export default reducer;
Demo.jsx组件中
import React from "react";
import { Button } from 'antd';
import { useSelector, useDispatch } from 'react-redux';
const Demo = function Demo() {
const { num } = useSelector(state => state.demo),
dispatch = useDispatch();
return <div>
<span style={{ fontSize: 20, paddingLeft: 10 }}>
{num}
</span>
<br />
<Button type="primary"
onClick={() => {
//基于dispatch进行派发....
}}>
按钮
</Button>
</div>;
};
export default Demo;
saga.js 工作流程
- take(pattern)
- takeEvery(pattern, saga, …args)
- takeLatest(pattern, saga, ..args)
- throttle(ms, pattern, saga, ..args)
- …
第二部分:创建执行函数「基于Effect创建器(API)」
- put(action)
- call(fn, …args)
- fork(fn, …args)
- select(selector, …args)
- …
每一次组件派发后发生的事情
每一次在组件中,基于 dispatch(action) 的时候:
- 首先会通知 reducer 执行
- 然后再去通知 saga 中的监听器执行
关于监听器创建的细节
组件中
<Button type="primary"
onClick={() => {
dispatch({
type: "DEMO-SAGA",
payload: 10
});
}}>
按钮
</Button>
saga.js -> take 函数的运用
import * as TYPES from './action-types';
const working = function* working(action) {
// 等价于 dispatch派发:通知reducer执行
yield put({
type: TYPES.DEMO,
payload: action.payload
});
};
export default function* saga() {
/* // 创建监听器,当监听到派发后,才会继续向下执行
// 特征:只能监听一次
// action:可以获取派发时传递的action
let action = yield take("DEMO-SAGA");
yield working(action); */
// 可基于循环,创建无限监听机制
while (true) {
let action = yield take("DEMO-SAGA");
yield working(action);
}
};
saga.js -> 其它监听器辅助函数的运用
const working = function* working(action) {
console.log('AAA');
// 设置延迟函数:等待2000ms后,才会继续向下执行!!
yield delay(2000);
yield put({
type: TYPES.DEMO,
payload: action.payload
});
};
export default function* saga() {
/* // 派发后,立即通知异步的working执行;
// 但是在working没有处理完毕之前,所有其他的派发任务都不在处理!!
while (true) {
let action = yield take("DEMO-SAGA");
yield working(action);
} */
/* // 每一次派发任务都会被执行
yield takeEvery("DEMO-SAGA", working); */
/* // 每一次派发任务都会被执行,但是会把之前没有处理完毕的干掉
yield takeLatest("DEMO-SAGA", working); */
/* // 每一次派发的任务会做节流处理;在频繁触发的操作中,1000ms内,只会处理一次派发任务
yield throttle(1000, "DEMO-SAGA", working); */
/* // 每一次派发的任务会做防抖处理;在频繁触发的操作中,只识别最后一次派发任务进行处理
yield debounce(1000, "DEMO-SAGA", working); */
};
saga.js -> yield call/select…
import * as TYPES from './action-types';
import http from '../api/http';
const working = function* working() {
// 获取目前的公共状态信息
let { num } = yield select(state => state.demo);
// 从服务器获取数据
// let result = yield apply(null, http.get, ['/api/news/latest']);
let result = yield call(http.get, '/api/news/latest');
console.log(result); //从服务器获取的数据
yield put({
type: TYPES.DEMO
});
};
export default function* saga() {
yield takeLatest("DEMO-SAGA", working);
};
saga.js -> yield fork
const query1 = function* query1() {
console.log(1);
yield delay(2000);
};
const query2 = function* query2() {
console.log(2);
yield delay(2000);
};
const working = function* working() {
/* // 串行
yield call(query1);
yield call(query2); */
/* // 并行:无阻塞调用
yield fork(query1);
yield fork(query2); */
console.log(3);
};
export default function* saga() {
yield takeLatest("DEMO-SAGA", working);
};
3. 基于redux-saga重写Vote案例
组件
import { useSelector, useDispatch } from 'react-redux';
import * as TYPES from '../store/action-types';
...
const Vote = function Vote() {
const { supNum, oppNum } = useSelector(state => state.vote),
dispatch = useDispatch();
return <VoteBox>
...
<div className="footer">
<Button type="primary"
onClick={() => {
dispatch({
type: TYPES.VOTE_SUP
});
}}>
支持
</Button>
<Button type="primary"
onClick={() => {
dispatch({
type: "VOTE-SUP-SAGA"
});
}}>
异步支持
</Button>
<Button type="primary" danger
onClick={() => {
dispatch({
type: TYPES.VOTE_OPP
});
}}>
反对
</Button>
<Button type="primary" danger
onClick={() => {
dispatch({
type: "VOTE-OPP-SAGA"
});
}}>
反对异步
</Button>
</div>
</VoteBox>;
};
export default Vote;
saga.js
import { takeLatest, put, delay } from 'redux-saga/effects';
import * as TYPES from './action-types';
const voteSupWorking = function* voteSupWorking() {
yield delay(2000);
yield put({
type: TYPES.VOTE_SUP
});
};
const voteOppWorking = function* voteOppWorking() {
yield delay(2000);
yield put({
type: TYPES.VOTE_OPP
});
};
export default function* saga() {
yield takeLatest("VOTE-SUP-SAGA", voteSupWorking);
yield takeLatest("VOTE-OPP-SAGA", voteOppWorking);
};