您好,登錄后才能下訂單哦!
本篇文章為大家展示了如何正確的使用redux-saga ,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
redux-saga 是一個管理 Redux 應用異步操作的中間件,功能類似redux-thunk + async/await, 它通過創(chuàng)建 Sagas 將所有的異步操作邏輯存放在一個地方進行集中處理。
redux-saga 的 effects
redux-saga中的 Effects 是一個純文本 JavaScript 對象,包含一些將被 saga middleware 執(zhí)行的指令。這些指令所執(zhí)行的操作包括如下三種:
發(fā)起一個異步調用(如發(fā)一起一個 Ajax 請求)
發(fā)起其他的 action 從而更新 Store
調用其他的 Sagas
Effects 中包含的指令有很多,具體可以異步API 參考進行查閱
redux-saga 的特點
方便測試,例如:
assert.deepEqual(iterator.next().value, call(Api.fetch, '/products'))
action 可以保持其純凈性,異步操作集中在 saga 中進行處理
watch/worker(監(jiān)聽->執(zhí)行) 的工作形式
被實現為 generator
對含有復雜異步邏輯的應用場景支持良好
更細粒度地實現異步邏輯,從而使流程更加清晰明了,遇到 bug 易于追蹤和解決。
以同步的方式書寫異步邏輯,更符合人的思維邏輯
從 redux-thunk 到 redux-saga
假如現在有一個場景:用戶在登錄的時候需要驗證用戶的 username 和 password 是否符合要求。
使用 redux-thunk 實現
獲取用戶數據的邏輯(user.js):
// user.js import request from 'axios'; // define constants // define initial state // export default reducer export const loadUserData = (uid) => async (dispatch) => { try { dispatch({ type: USERDATA_REQUEST }); let { data } = await request.get(`/users/${uid}`); dispatch({ type: USERDATA_SUCCESS, data }); } catch(error) { dispatch({ type: USERDATA_ERROR, error }); } }
驗證登錄的邏輯(login.js):
import request from 'axios'; import { loadUserData } from './user'; export const login = (user, pass) => async (dispatch) => { try { dispatch({ type: LOGIN_REQUEST }); let { data } = await request.post('/login', { user, pass }); await dispatch(loadUserData(data.uid)); dispatch({ type: LOGIN_SUCCESS, data }); } catch(error) { dispatch({ type: LOGIN_ERROR, error }); } }
redux-saga
異步邏輯可以全部寫進 saga.js 中:
export function* loginSaga() { while(true) { const { user, pass } = yield take(LOGIN_REQUEST) //等待 Store 上指定的 action LOGIN_REQUEST try { let { data } = yield call(loginRequest, { user, pass }); //阻塞,請求后臺數據 yield fork(loadUserData, data.uid); //非阻塞執(zhí)行l(wèi)oadUserData yield put({ type: LOGIN_SUCCESS, data }); //發(fā)起一個action,類似于dispatch } catch(error) { yield put({ type: LOGIN_ERROR, error }); } } } export function* loadUserData(uid) { try { yield put({ type: USERDATA_REQUEST }); let { data } = yield call(userRequest, `/users/${uid}`); yield put({ type: USERDATA_SUCCESS, data }); } catch(error) { yield put({ type: USERDATA_ERROR, error }); } }
難點解讀
對于 redux-saga, 還是有很多比較難以理解和晦澀的地方,下面筆者針對自己覺得比較容易混淆的概念進行整理:
take 的使用
take 和 takeEvery 都是監(jiān)聽某個 action, 但是兩者的作用卻不一致,takeEvery 是每次 action 觸發(fā)的時候都響應,而 take 則是執(zhí)行流執(zhí)行到 take 語句時才響應。takeEvery 只是監(jiān)聽 action, 并執(zhí)行相對應的處理函數,對何時執(zhí)行 action 以及如何響應 action 并沒有多大的控制權,被調用的任務無法控制何時被調用,并且它們也無法控制何時停止監(jiān)聽,它只能在每次 action 被匹配時一遍又一遍地被調用。但是 take 可以在 generator 函數中決定何時響應一個 action 以及 響應后的后續(xù)操作。
例如在監(jiān)聽所有類型的 action 觸發(fā)時進行 logger 操作,使用 takeEvery 實現如下:
import { takeEvery } from 'redux-saga' function* watchAndLog(getState) { yield* takeEvery('*', function* logger(action) { //do some logger operation //在回調函數體內 }) }
使用 take 實現如下:
import { take } from 'redux-saga/effects' function* watchAndLog(getState) { while(true) { const action = yield take('*') //do some logger operation //與 take 并行 }) }
其中 while(true) 的意思是一旦到達流程最后一步(logger),通過等待一個新的任意的 action 來啟動一個新的迭代(logger 流程)。
阻塞和非阻塞
call 操作是用來發(fā)起異步操作的,對于 generator 來說,call 是阻塞的操作,它在 Generator 調用結束之前不能執(zhí)行或處理任何其他事情。,但是 fork 卻是非阻塞操作,當 fork 調動任務時,該任務會在后臺執(zhí)行,此時的執(zhí)行流可以繼續(xù)往后面執(zhí)行而不用等待結果返回。
例如如下的登錄場景:
function* loginFlow() { while(true) { const {user, password} = yield take('LOGIN_REQUEST') const token = yield call(authorize, user, password) if(token) { yield call(Api.storeItem({token})) yield take('LOGOUT') yield call(Api.clearItem('token')) } } }
若在 call 在去請求 authorize 時,結果未返回,但是此時用戶又觸發(fā)了 LOGOUT 的 action,此時的 LOGOUT 將會被忽略而不被處理,因為 loginFlow 在 authorize 中被堵塞了,沒有執(zhí)行到 take('LOGOUT')那里
同時執(zhí)行多個任務
如若遇到某個場景需要同一時間執(zhí)行多個任務,比如 請求 users 數據 和 products 數據, 應該使用如下的方式:
import { call } from 'redux-saga/effects' //同步執(zhí)行 const [users, products] = yield [ call(fetch, '/users'), call(fetch, '/products') ] //而不是 //順序執(zhí)行 const users = yield call(fetch, '/users'), products = yield call(fetch, '/products')
當 yield 后面是一個數組時,那么數組里面的操作將按照 Promise.all 的執(zhí)行規(guī)則來執(zhí)行,genertor 會阻塞知道所有的 effects 被執(zhí)行完成
源碼解讀
在每一個使用 redux-saga 的項目中,主文件中都會有如下一段將 sagas 中間件加入到 Store 的邏輯:
const sagaMiddleware = createSagaMiddleware({sagaMonitor}) const store = createStore( reducer, applyMiddleware(sagaMiddleware) ) sagaMiddleware.run(rootSaga)
其中 createSagaMiddleware 是 redux-saga 核心源碼文件 src/middleware.js 中導出的方法:
export default function sagaMiddlewareFactory({ context = {}, ...options } = {}) { ... function sagaMiddleware({ getState, dispatch }) { const channel = stdChannel() channel.put = (options.emitter || identity)(channel.put) sagaMiddleware.run = runSaga.bind(null, { context, channel, dispatch, getState, sagaMonitor, logger, onError, effectMiddlewares, }) return next => action => { if (sagaMonitor && sagaMonitor.actionDispatched) { sagaMonitor.actionDispatched(action) } const result = next(action) // hit reducers channel.put(action) return result } } ... }
這段邏輯主要是執(zhí)行了 sagaMiddleware(),該函數里面將 runSaga 賦值給 sagaMiddleware.run 并執(zhí)行,最后返回 middleware。 接著看 runSaga() 的邏輯:
export function runSaga(options, saga, ...args) { ... const task = proc( iterator, channel, wrapSagaDispatch(dispatch), getState, context, { sagaMonitor, logger, onError, middleware }, effectId, saga.name, ) if (sagaMonitor) { sagaMonitor.effectResolved(effectId, task) } return task }
這個函數里定義了返回了一個 task 對象,該 task 是由 proc 產生的,移步 proc.js:
export default function proc( iterator, stdChannel, dispatch = noop, getState = noop, parentContext = {}, options = {}, parentEffectId = 0, name = 'anonymous', cont, ) { ... const task = newTask(parentEffectId, name, iterator, cont) const mainTask = { name, cancel: cancelMain, isRunning: true } const taskQueue = forkQueue(name, mainTask, end) ... next() return task function next(arg, isErr){ ... if (!result.done) { digestEffect(result.value, parentEffectId, '', next) } ... } }
其中 digestEffect 就執(zhí)行了 effectTriggerd() 和 runEffect(),也就是執(zhí)行 effect,其中 runEffect() 中定義了不同 effect 執(zhí)行相對應的函數,每一個 effect 函數都在 proc.js 實現了。
上述內容就是如何正確的使用redux-saga ,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。