redux-saga GitHub地址
https://github.com/yelouafi/redux-saga
redux-saga 官方文檔
redux-saga 簡介
一種專門用來處理異步請(qǐng)求(例如:fetch)的redux插件
使用方式
npm install --save redux-saga
當(dāng)然也支持UMD的加載方式
https://unpkg.com/redux-saga/dist/redux-saga.js
https://unpkg.com/redux-saga/dist/redux-saga.min.js
以前的方法有什么缺點(diǎn)
通過redux-thunk使用action創(chuàng)造器做一些流程控制的事情示弓。這樣可能會(huì)很難測(cè)試柄慰,但是管用。
// an action creator with thunking
function createRequest () {
return function (dispatch, getState) {
dispatch({ type: 'REQUEST_STUFF' })
someApiCall(function(response) {
// some processing
dispatch({ type: 'RECEIVE_STUFF' })
}
}
}
可能在組件中還會(huì)寫到
function onHandlePress () {
this.props.dispatch({ type: 'SHOW_WAITING_MODAL' })
this.props.dispatch(createRequest())
}
使用redux狀態(tài)樹(和reducers)來扮演一個(gè)信號(hào)系統(tǒng)搏恤,把東西鏈接在一起。 到處都是代碼
Saga長什么樣子?
Saga其實(shí)是generator生成器函數(shù)
function *hello() {
}
對(duì)thunk的改進(jìn)
function *hello() {
yield take('BEGIN_REQUEST')
yield put({ type: 'SHOW_WAITING_MODAL' })
const response = yield call(myApiFunctionThatWrapsFetch)
yield put({ type: 'PRELOAD_IMAGES', response.images })
yield put({ type: 'USAGE_TRACK', activity: 'API_CALL'})
yield put({ type: 'HIDE_WAITING_MODAL' })
}
示例
[圖片上傳失敗...(image-d8de54-1527745550799)]
如果簡單的用react-redux實(shí)現(xiàn),缺點(diǎn)是:組件視圖里面會(huì)混入一些應(yīng)用邏輯代碼
class Timer extends Component {
componentWillReceiveProps(nextProps) {
const { state: { status: currStatus } } = this.props;
const { state: { status: nextStatus } } = nextProps;
if (currState === 'Stopped' && nextState === 'Running') {
this._startTimer();
} else if (currState === 'Running' && nextState === 'Stopped') {
this._stopTimer();
}
}
_startTimer() {
this._intervalId = setInterval(() => {
this.props.tick();
}, 1000);
}
_stopTimer() {
clearInterval(this._intervalId);
}
// ...
}
如果采用redux-thunk,我們可以這樣寫,缺點(diǎn)是:簡單地將應(yīng)用邏輯引入到了action中遏插,導(dǎo)致action過于復(fù)雜,并且不利于測(cè)試
export default {
start: () => (
(dispatch, getState) => {
// This transitions state to Running
dispatch({ type: 'START' });
// Check every 1 second if we are still Running.
// If so, then dispatch a `TICK`, otherwise stop
// the timer.
const intervalId = setInterval(() => {
const { status } = getState();
if (status === 'Running') {
dispatch({ type: 'TICK' });
} else {
clearInterval(intervalId);
}
}, 1000);
}
)
// ...
};
采用redux-saga纠修,就可以完美地單獨(dú)構(gòu)建應(yīng)用邏輯
function* runTimer(getState) {
// The sagasMiddleware will start running this generator.
// Wake up when user starts timer.
while(yield take('START')) {
while(true) {
// This side effect is not run yet, so it can be treated
// as data, making it easier to test if needed.
yield call(wait, ONE_SECOND);
// Check if the timer is still running.
// If so, then dispatch a TICK.
if (getState().status === 'Running') {
yield put(actions.tick());
// Otherwise, go idle until user starts the timer again.
} else {
break;
}
}
}
}
常用API
初始化API
Middleware API
createSagaMiddleware(options)
用于創(chuàng)建一個(gè)中間件并連接saga和redux胳嘲。
middleware.run(saga, ...args)
啟動(dòng)saga,此方法能自動(dòng)檢測(cè)yield的執(zhí)行扣草,當(dāng)前yield返回時(shí)了牛,會(huì)執(zhí)行next(result)颜屠,result是上一個(gè)yield返回的結(jié)果。一般saga的Generator函數(shù)會(huì)寫成try/catch的形式鹰祸,當(dāng)執(zhí)行出錯(cuò)是就會(huì)執(zhí)行catch甫窟。如果saga在中途被cancel掉了,就會(huì)執(zhí)行Generator里的return蛙婴。
上層API
takeEvery(pattern, saga, ...args)
相當(dāng)于redux-thunk的執(zhí)行模式粗井,響應(yīng)匹配到的狀態(tài)多少次,執(zhí)行多少次,但是不會(huì)根據(jù)響應(yīng)的次序返回response街图。
takeLatest(pattern, saga, ...args)
如果上一次異步響應(yīng)還沒有返回浇衬,此時(shí)又接收到一次匹配的狀態(tài),則會(huì)cancel掉前一次的請(qǐng)求餐济,去執(zhí)行最新的一次請(qǐng)求耘擂。
##### 底層API
take(pattern)
相對(duì)于takeEvery,更底層絮姆,但是更實(shí)用,實(shí)例代碼如下:
function* watchAndLog() {
yield takeEvery('*', function* logger(action) {
const state = yield select()
console.log('action', action)
console.log('state after', state)
})
}
function* loginFlow() {
while (true) {
yield take('LOGIN')
// ... perform the login logic
yield take('LOGOUT')
// ... perform the logout logic
}
}
使用Channels
import { take, fork, ... } from 'redux-saga/effects'
function* watchRequests() {
while (true) {
const {payload} = yield take('REQUEST')
yield fork(handleRequest, payload)
}
}
function* handleRequest(payload) { ... }
actionChannel
actionChannel用來接收redux store內(nèi)部狀態(tài)消息
import { take, actionChannel, call, ... } from 'redux-saga/effects'
function* watchRequests() {
// 1- Create a channel for request actions
const requestChan = yield actionChannel('REQUEST')
while (true) {
// 2- take from the channel
// const requestChan = yield actionChannel('REQUEST', buffers.sliding(5))
const {payload} = yield take(requestChan)
// 3- Note that we're using a blocking call
yield call(handleRequest, payload)
}
}
function* handleRequest(payload) { ... }
eventChannel
eventChannel用來接收redux store之外的消息
import { take, put, call, cancelled } from 'redux-saga/effects'
import { eventChannel, END } from 'redux-saga'
// creates an event Channel from an interval of seconds
function countdown(seconds) { ... }
export function* saga() {
const chan = yield call(countdown, value)
try {
while (true) {
let seconds = yield take(chan)
console.log(`countdown: ${seconds}`)
}
} finally {
if (yield cancelled()) {
chan.close()
console.log('countdown cancelled')
}
}
}
channel
channel用在多個(gè)saga之間通信
import { channel } from 'redux-saga'
import { take, fork, ... } from 'redux-saga/effects'
function* watchRequests() {
// create a channel to queue incoming requests
const chan = yield call(channel)
// create 3 worker 'threads'
for (var i = 0; i < 3; i++) {
yield fork(handleRequest, chan)
}
while (true) {
const {payload} = yield take('REQUEST')
yield put(chan, payload)
}
}
function* handleRequest(chan) {
while (true) {
const payload = yield take(chan)
// process the request
}
}