這篇文章主要介紹了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
對含有複雜非同步邏輯的應用場景支援良好
使用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 还提供了一系列的 helper 文件,这些文件的作用是返回一个类 iterator 的对象,便于后续的遍历和执行, 在此不具体分析。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
以上是如何使用redux-saga,使用redux-saga又有哪些方法和技巧?的詳細內容。更多資訊請關注PHP中文網其他相關文章!