您好,登錄后才能下訂單哦!
本篇文章為大家展示了如何正確的使用redux-saga ,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
redux-saga 是一個管理 Redux 應用異步操作的中間件,功能類似redux-thunk + async/await, 它通過創建 Sagas 將所有的異步操作邏輯存放在一個地方進行集中處理。
redux-saga 的 effects
redux-saga中的 Effects 是一個純文本 JavaScript 對象,包含一些將被 saga middleware 執行的指令。這些指令所執行的操作包括如下三種:
發起一個異步調用(如發一起一個 Ajax 請求)
發起其他的 action 從而更新 Store
調用其他的 Sagas
Effects 中包含的指令有很多,具體可以異步API 參考進行查閱
redux-saga 的特點
方便測試,例如:
assert.deepEqual(iterator.next().value, call(Api.fetch, '/products'))
action 可以保持其純凈性,異步操作集中在 saga 中進行處理
watch/worker(監聽->執行) 的工作形式
被實現為 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); //非阻塞執行loadUserData yield put({ type: LOGIN_SUCCESS, data }); //發起一個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 都是監聽某個 action, 但是兩者的作用卻不一致,takeEvery 是每次 action 觸發的時候都響應,而 take 則是執行流執行到 take 語句時才響應。takeEvery 只是監聽 action, 并執行相對應的處理函數,對何時執行 action 以及如何響應 action 并沒有多大的控制權,被調用的任務無法控制何時被調用,并且它們也無法控制何時停止監聽,它只能在每次 action 被匹配時一遍又一遍地被調用。但是 take 可以在 generator 函數中決定何時響應一個 action 以及 響應后的后續操作。
例如在監聽所有類型的 action 觸發時進行 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 操作是用來發起異步操作的,對于 generator 來說,call 是阻塞的操作,它在 Generator 調用結束之前不能執行或處理任何其他事情。,但是 fork 卻是非阻塞操作,當 fork 調動任務時,該任務會在后臺執行,此時的執行流可以繼續往后面執行而不用等待結果返回。
例如如下的登錄場景:
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 時,結果未返回,但是此時用戶又觸發了 LOGOUT 的 action,此時的 LOGOUT 將會被忽略而不被處理,因為 loginFlow 在 authorize 中被堵塞了,沒有執行到 take('LOGOUT')那里
同時執行多個任務
如若遇到某個場景需要同一時間執行多個任務,比如 請求 users 數據 和 products 數據, 應該使用如下的方式:
import { call } from 'redux-saga/effects' //同步執行 const [users, products] = yield [ call(fetch, '/users'), call(fetch, '/products') ] //而不是 //順序執行 const users = yield call(fetch, '/users'), products = yield call(fetch, '/products')
當 yield 后面是一個數組時,那么數組里面的操作將按照 Promise.all 的執行規則來執行,genertor 會阻塞知道所有的 effects 被執行完成
源碼解讀
在每一個使用 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 } } ... }
這段邏輯主要是執行了 sagaMiddleware(),該函數里面將 runSaga 賦值給 sagaMiddleware.run 并執行,最后返回 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 就執行了 effectTriggerd() 和 runEffect(),也就是執行 effect,其中 runEffect() 中定義了不同 effect 執行相對應的函數,每一個 effect 函數都在 proc.js 實現了。
上述內容就是如何正確的使用redux-saga ,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。