前言
在使用react redux的時候,會經常遇到需要處理異步action的情況。處理異步action的方法有幾種。其中redux-thunk,redux-saga都是處理異步action的中間件。利用這些中間件可以很好的達到我們預期效果
redux-saga
redux-saga
是一個用于管理應用程式 Side Effect(副作用,例如異步擷取資料,通路浏覽器緩存等)的 library,它的目标是讓副作用管理更容易,執行更高效,測試更簡單,在處理故障時更容易。
個人是這麼了解redux-saga的,在app中注入redux-saga中間件後,saga effects函數中對相對應的action進行監聽,在代碼中執行觸發dispatch 對應action的時候,saga的effects 函數會對其進行攔截處理,中途進行一些異步、同步、或者是執行調用其他effects函數。在effects函數中,可以同步的方式書寫異步代碼。相對于redux-thunk,saga的優勢在于effects函數中對各種異步的處理比較友善和容易拓展。
計數器demo例子
Counter 元件
import React, { Component, PropTypes } from 'react'
const Counter = ({ value, onIncrement, onDecrement, onIncrementAsync }) =>
<div>
<button onClick={onIncrement}>
Increment
</button>
{' '}
<button onClick={onDecrement}>
Decrement
</button>
<button onClick={onIncrementAsync}>
delay Increment
</button>
<hr />
<div>
Clicked: {value} times
</div>
</div>
Counter.propTypes = {
value: PropTypes.number.isRequired,
onIncrement: PropTypes.func.isRequired,
onDecrement: PropTypes.func.isRequired,
onIncrementAsync: PropTypes.func.isRequired
}
export default Counter
main.js
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootSaga from './saga'
import Counter from './Counter'
import reducer from './reducers'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga)
const action = type => store.dispatch({type})
function render() {
ReactDOM.render(
<Counter
value={store.getState()}
onIncrement={() => action('INCREMENT')}
onDecrement={() => action('DECREMENT')}
onIncrementAsync={() => action('INCREMENT_ASYNC')} />,
document.getElementById('root')
)
}
render()
store.subscribe(render)
saga.js
import { delay } from 'redux-saga'
import { put, takeEvery, all } from 'redux-saga/effects'
export function* incrementAsync() {
yield delay(1000)
yield put({ type: 'INCREMENT' })
}
export function* watchIncrementAsync() {
yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}
export default function* rootSaga() {
yield all([
watchIncrementAsync()
])
}
在計數器中有三個按鈕
Increment
、
Decrement
Delay Increment
點選
Increment
,計數馬上加1,點選
Decrement
,計數馬上減1,點選
Delay Increment
計數延遲1秒加1
原了解析在saga中,對于
INCREMENT_ASYNC
這個action進行了監聽,如果觸發了這個action則會執行saga中的incrementAsync函數,在該函數中,有delay延遲函數。yield delay(1000)的意思是等待延遲1秒,然後執行yield put(…),執行觸發
INCREMENT
action,然後觸發reducer 對state的值的修改。
整個流程 監聽
INCREMENT_ASYNC
,觸發
INCREMENT_ASYNC
,執行incrementAsync,dispatch
INCREMENT
,最後修改state,觸發UI更新。在saga的effects函數中可以對異步處理的結果進行再次處理整合,最後再派發執行下一步。整個流程是有序執行可監控的。這樣我們就簡單了解了redux-saga的原理和流程,進一步學習redux-saga則需要對API有更深入的了解
進階學習redux-saga
- take
- fork
- takeEvery
- takeLatest
takeLatest & takeEvery 是監聽action,一遍一遍執行,不會停止。takeLatest與takeEvery的差別在于,如果同個action多次觸發,takeLatest隻會執行最後一個action,往前的action會取消調用,而takeEvery則每個action均會執行調用。take則隻執行一次。fork則在背景建立任務監聽,非阻塞主流程。是以會經常使用 fork+ while(true)+take的方式實作可控制的effect函數。檢視例子
import { fork, call, take, put } 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})
} catch(error) {
yield put({type: 'LOGIN_ERROR', error})
}
}
function* loginFlow() {
while(true) {
const {user, password} = yield take('LOGIN_REQUEST')
yield fork(authorize, user, password)
yield take(['LOGOUT', 'LOGIN_ERROR'])
yield call(Api.clearItem('token'))
}
}
參考資料
- redux-saga中文教程