本文适合对Redux有必定了解,或者重度失眠患者阅读!html
由于本文主要讲Redux-Saga,故action、view、reducer这块就快速掠过;第一步发送一个action,好让Saga那边监听到!前端
来到Saga这边,直接上代码!!react
// homeSaga.js
import {
takeLatest, // 短期内(没有执行完函数)屡次触发的状况下,指会触发相应的函数一次
// takeEvery, // takeLatest 的同胞兄弟,不一样点是每次都会触发相应的函数
put, // 做用跟 dispatch 一毛同样,能够就理解为dispatch
call // fork 的同胞兄弟,不过fork是非阻塞,call是阻塞,阻塞的意思就是到这块就停下来了
} from 'redux-saga/effects';
import * as actions from '../actions/homeAction';
import fetch from '../utils/fetch';
export default function* rootSaga () {
yield takeLatest('GET_DATA_REQUEST', getDataSaga); // 就在这个rootSaga里面利用takeLatest去监听action的type
}
function* getDataSaga(action) {
try {
yield put(actions.requestDataAction(true, 'LOADING')); // 开启loading
const userName = action.payload;
// 一、也能够这么写: const result = yield fetch(url地址, params);
// 二、这边用 call 是为了之后须要的 saga 测试
// https://api.github.com/users/userName 是github的我的信息
const url = `https://api.github.com/users/${userName}`;
const api = (params) => fetch(url, params);
const result = yield call(api);
if (result) {
// 成功后:即将在 reducer 里作你想作的事情
yield put(actions.requestDataAction(result, 'SUCCESS'));
}
} catch (e) {
// 失败后:即将在 reducer 里作你想作的事情
yield put(actions.requestDataAction(e, 'ERROR'));
} finally {
// 无论成功仍是失败仍是取消等,都会通过这里
yield put(actions.requestDataAction(false, 'LOADING')); // 关闭loading
yield put(actions.requestDataAction(null, 'FINISH')); // 打印一个结束的action,通常没什么用
}
}
复制代码
rootSaga
能够理解为是一个监听函数,在建立store中间件的时候就已经执行了;rootSaga
里面经过引入的 takeLatest
去去监听刚才的的action.type: 'GET_DATA_REQUEST', 咱们去看下takeLatest
的源码ios
const takeLatest = (patternOrChannel, saga, ...args) => fork(function*() {
let lastTask
while (true) {
const action = yield take(patternOrChannel)
if (lastTask) {
yield cancel(lastTask) // cancel is no-op if the task has already terminated
}
lastTask = yield fork(saga, ...args.concat(action))
}
})
复制代码
经过源码的看出来,这个takeLatest
是也是由redux-saga的 fork 与 take 构成的高阶函数,若是按官网的详细解释,能够写好几页了,这边主要记住这几点就够了!
fork
:git
fork
是非阻塞的,非阻塞就是遇到它,不须要等它执行完, 就能够直接往下运行;fork
的另一个同胞兄弟call是阻塞,阻塞的意思就是必定要等它执行完, 才能够直接往下运行;take
:take
是阻塞的,主要用来监听action.type
的,只有监听到了,才会继续往下执行;从上面的解释,会有点跟咱们的对程序运行的认知不太同样,由于当这个 takeLatest
高阶函数运行到github
const action = yield take(patternOrChannel)
复制代码
这一段时,这个函数就停在这里了,只有当这个take
监听到action.type
的时候,才会继续往下执行;
因此,rootSaga
函数执行的时候,yield takeLatest('GET_DATA_REQUEST', getDataSaga);
也执行了,也就是运行到const action = yield take(patternOrChannel)
这步停下来,监听之后发出的 GET_DATA_REQUEST
;当咱们点击按钮发出这个type为GET_DATA_REQUEST
的action,那么这个take
就监听到,从而就继续往下运行redux
if (lastTask) {
yield cancel(lastTask)
}
复制代码
这一段的意思就是区别takeLatest与它的同胞兄弟takeEvery的区别,takeLatest
是在他的程序没运行完时,再次运行时,会取消它的上一个任务;而takeEvery
则是运行都会fork一个新的任务出来,不会取消上一个任务;因此,用takeLatest
来处理重复点击的问题,无敌好用!api
lastTask = yield fork(saga, ...args.concat(action))
复制代码
最后这句就是运行takeLatest
里的函数,经过ES6的REST语法
,传相应的参数过去,若是在takeLatest
里面没有传第三个及以上的参数,那么就只传这个take
监听到的action
过去;
因此因此,对rootSaga
函数里面这个 yield takeLatest('GET_DATA_REQUEST', getDataSaga)
说了那么多,能够理解为就是一句话,监听action.type
为GET_DATA_REQUEST
的action,并运行getDataSaga(action)
这个函数;
对了,只要是Generator函数
,要加 * 号啊!!
数组
程序运行到getDataSaga
这个函数,推荐写法是加入try catch
写法promise
try {
// 主要程序操做点
} catch(e) {
// 捕捉到错误后,才会运行的地方
} finally {
// 任何状况下都会走到这里,若是非必要的状况下,能够省略 finally
}
复制代码
具体每一步的做用都用注释的方式写出来了,仍是比较直观的!这里再对一些生面孔说明一下,
put
:你就认为put
就等于 dispatch
就能够了;call
:刚才已经解释过了,阻塞型的,只有运行完后面的函数,才会继续往下;在这里能够片面的理解为promise
里的then
吧!但写法直观多了!好了,里面的每一个put(action)
就至关dispatch(action)
出去,reducer
边接收到相应的action.type
就会对数据进行相应的操做,最后经过react-redux
的connect
回到视图中,完成了一次数据驱动视图,也是什么所谓的MVVM
刚才是最正常的状况下走了一遍Redux-Saga
,那假如产品在这个基础上,提了要求:再正在请求的Dan数据的时候,能够手动取消这个异步请求呢? 相信这需求对于前端的小伙伴来讲,仍是比较难的吧!
还记得刚才对fork
解释的三点吗?其中有第三点就介绍了fork
是能够取消的。
刚才是说rootSaga
里的takeLatest
负责监听,getDataSaga
负责执行,那要想控制这个执行函数,则要在这两个函数中间再插入一个函数,就变成了takeLatest
监听到GET_DATA_REQUEST
后,去执行这个控制函数,直接看代码
为了更加方便的查看效果,咱们手动加入延迟
import { delay } from 'redux-saga';
...
try {
...
yield delay(2000); // 手动延迟2秒
...
}
...
复制代码
这是点击肯定按钮,在请求的过程当中,点击取消按钮,就发现这个异步被取消了!!完美解决!!!
这里要轻喷一下,Redux-Saga
官网推荐的Redux-Saga中文文档,里面有错误的地方,也没修正;一样来自Redux-Saga
官网推荐Redux-Saga繁体文档就没问题!- -!!!
这时候,加入产品在以上基础上,再次提了要求:不光能够手动取消这个异步请求,还要加入超时自动取消这个异步请求,超时时间为5秒呢? 这让我想到了上古时代的AJAX
, 那时候封装好的AJAX
都是会有个timeOut 默认5秒给咱们,超过了这个timeOut,就会自动取消异步请求
Vue.Js
中大红大紫的异步插件Axios
有这个功能!而这里的演示是彻底利用Redux-Saga
这个强大到变态的功能来解决超时自动取消的问题的,没使用Axios
......- -!答案就是Redux-Saga
自带的race,用一句话解释就是,队列里面,谁先了就用谁,抛弃其余!骚微改造一下controlSaga
这个函数
function* controlSaga (action) {
const task = yield fork(getDataSaga, action); // 运行getDataSaga这个任务
yield race([ // 利用rece谁先来用谁的原则,完美解决 超时自动取消与手动取消的 的问题
take('CANCEL_REQUEST'), // 到这步时就阻塞住了,直到发出type为'CANCEL_REQUEST'的action,才会继续往下
call(delay, 1000) // 控制时间
]);
yield cancel(task); // 取消任务,取消后,cancelled() 会返回true,不然返回false
}
复制代码
由于咱们刚才在try{...}
里面加入了yield delay(2000)
延时两秒,为了保证超时间必定快过异步请求时间,这边的超时时间咱们用1秒。而后点击确认按钮,在什么都不作的状况下,就能够看到请求一下后,自动就取消了!完美...(通常默认的timeOut为5秒)
F12
观看异步请求,能够更清晰直观controlSaga
// controlSaga.js
import { take, fork, race, call, cancel, put } from 'redux-saga/effects';
import { delay } from 'redux-saga';
// 普通函数,故不须要加 *
function controlSaga (fn) {
// 返回一个 Generator函数
/** * @param timeOut: 超时时间, 单位 ms, 默认 5000ms * @param cancelType: 取消任务的action.type * @param showInfo: 打印信息 默认不打印 */
return function* (...args) {
// 这边思考了一下,仍是单单传action过去吧,不想传args这个数组过去, 感受没什么意义
const task = yield fork(fn, args[args.length - 1]);
const timeOut = args[0].timeOut || 5000; // 默认5秒
// 若是真的使用这个controlSaga函数的话,通常都会传取消的type过来, 假如真的不传的话,配合Match.random()也能避免误伤
const cancelType = args[0].cancelType || `NOT_CANCEL${Math.random()}`;
const showInfo = args[0].showInfo; // 没什么用,打印信息而已
const result = yield race({
timeOut: call(delay, timeOut),
// 实际业务需求
handleToCancel: take(cancelType)
});
if (showInfo) {
if (result.timeOut) yield put({type: `超过规定时间${timeOut}ms后自动取消`})
if (result.handleToCancel) yield put({type: `手动取消,action.type为${cancelType}`})
}
yield cancel(task);
}
}
export default controlSaga;
复制代码
takeLatest
第二个参数是用controlSaga(fn)
包裹住,而后经过第三个参数往controlSaga
里面传控制参数便可,超方便供人使用的- -.V