react系列筆記:第三記-redux-saga
github : https://github.com/redux-saga/redux-saga
文檔:https://redux-saga.js.org/
redux-saga: redux中間件,旨在處理應(yīng)用中的副作用
使用:
import createSagaMiddleware from 'redux-saga' import {createStore,applyMiddleware} from 'redux' const saga = createSagaMiddleware(); const store = createStore( reducer, applyMiddleware(sagaMiddleware) )
sagaMiddleware.run(mysagas);
基礎(chǔ)概念:
saga-middleware 檢查每個被 yield 的 Effect 的類型,然后決定如何實現(xiàn)哪個 Effect。如果 Effect 類型是 PUT 那 middleware 會 dispatch 一個 action 到 Store。 如果 Effect 類型是 CALL 那么它會調(diào)用給定的函數(shù)。
put({type: 'INCREMENT'}) // => { PUT: {type: 'INCREMENT'} }
call(delay, 1000) // => { CALL: {fn: delay, args: [1000]}}
輔助函數(shù):
takeEvery : 可以同時啟動多次任務(wù)
takeLatest : 在一次任務(wù)未完成之前,遇到新任務(wù)將取消之前任務(wù)。
聲明式的effect
call: saga通過 Generator函數(shù)實現(xiàn),在yield函數(shù)后執(zhí)行effect,其中call是用于執(zhí)行某些異步操作的。
yield requestSome('/xxx');
yield call(requestSome,'/xxx')
//之所以用call取代上面的直接調(diào)用寫法,好處在于,
//編寫測試代碼的時候,可以deepEqual generator函數(shù)的next().value和call(requestSome,'/xxx'),這樣所有的effect都可以被測試,而不需要mock yield后的執(zhí)行結(jié)果
dispatching actions:
put : 和上面的call一樣,中間件提供put 來把action丟到中間件中去dispatch,好處同樣是便于測試
高級:
take:
call方法可以yield一個promise,然后會阻塞generator的執(zhí)行,知道promise resolve,結(jié)果返回
take(xxx)同理,阻塞generator直到xxx匹配的action被觸發(fā)
由此:take(*)可以用于抓取log,take(*)會匹配任意的action觸發(fā)事件
使用while(true){ take(/xxx/) } 可以創(chuàng)建持續(xù)的action監(jiān)聽,當然也可以根據(jù)需求,選擇性的監(jiān)聽,只需改版while(xxx)的條件就行了
function* actionLog(){ while(true){ yield take('INCREMENT'); console.log('do increment'); yield take('DECREMENT'); console.log('do decrement') } } //take可以控制action的監(jiān)聽順序,如上,會先監(jiān)聽到INCREMENT之后,才會再往下監(jiān)聽DECREMENT,完后繼續(xù)監(jiān)聽INCREMENT。這在一些順序明確的action事件里,可以將流程代碼寫在一起, //如login logout
fork:
fork和take不同,take會和call一樣阻塞代碼的執(zhí)行,知道結(jié)果返回,fork則不會,它會將任務(wù)啟動并且不阻塞代碼的執(zhí)行,
fork會返回一個task,可以用cacel(task)來取消任務(wù)
https://redux-saga.js.org/docs/advanced/NonBlockingCalls.html
此文中將login 和logout作為例子,在login請求未返回來之前執(zhí)行了logout,則需要cacel未完成的login,又不能用take(login)否則阻塞logout,會在login響應(yīng)之前take不到logout。
import { take, put, call, fork, cancel } from 'redux-saga/effects'
// ...
function* loginFlow() {
while (true) {
const {user, password} = yield take('LOGIN_REQUEST')
// fork return a Task object
const task = yield fork(authorize, user, password)
const action = yield take(['LOGOUT', 'LOGIN_ERROR'])
if (action.type === 'LOGOUT')
yield cancel(task)
yield call(Api.clearItem, 'token')
}
}
import { take, call, put, cancelled } from 'redux-saga/effects'
import Api from '...'
function* authorize(user, password) {
try {
const token = yield call(Api.authorize, user, password)
yield put({type: 'LOGIN_SUCCESS', token})
yield call(Api.storeItem, {token})
return token
} catch(error) {
yield put({type: 'LOGIN_ERROR', error})
} finally {
if (yield cancelled()) {
// ... put special cancellation handling code here
}
}
}
all:
yield表達式,可以將語句分段執(zhí)行,但如果有時候想同時執(zhí)行兩個任務(wù),則需要用到all
import {all,call} from 'redux-saga/effect'
//此處會同步執(zhí)行兩個call的任務(wù)
const [users, repos] = yield all([
call(fetch, '/users'),
call(fetch, '/repos')
])
race:
和promise中的race一個概念,執(zhí)行多個任務(wù),受到響應(yīng)后則繼續(xù)執(zhí)行
function* fetchPostsWithTimeout() { const {posts, timeout} = yield race({ posts: call(fetchApi, '/posts'), timeout: call(delay, 1000) }) if (posts) put({type: 'POSTS_RECEIVED', posts}) else put({type: 'TIMEOUT_ERROR'}) }
yield *
通過yield * xxx()來組合多個generator任務(wù)。
組合saga:
可以通過yield [call(task1),call(task2)...]來組合多個generator。
channels:
throttle:
節(jié)流
delay:
防抖動
浙公網(wǎng)安備 33010602011771號