本文適合對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