文章首發於語雀 探究redux的反作用處理以及dispatch的promiseify 源碼分析javascript
未經容許嚴禁轉載java
咱們知道redux的做用是管理應用程序的狀態,讓複雜應用可以更好得處理數據,使視圖與數據一一對應。那redux的運行機制是什麼樣的呢?大體講一下,就是用戶行爲產生action,dispatch接收action,而後經過reducer處理後生成新的state,最後更新store以及視圖。看👇圖react
redux的設計思想就是不產生反作用,數據更改的狀態可回溯,因此redux中到處都是純函數。git
那怎麼處理反作用呢?redux沒有提供解決直接的方案。可是它提供一箇中間件機制,讓用戶去開發反作用處理的中間件。不少優秀的中間件也隨着出現,如redux-thunk、redux-promise、redux-saga等。那它們是如何處理反作用的呢?請看👇github
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
複製代碼
redux-thunk最重要的思想,就是能夠接受一個返回函數的 action creator
。若是這個 action creator
返回的是一個函數,就執行它,若是不是,就按照原來的 next(action)
執行。 正由於這個 action creator
能夠返回一個函數,那麼就能夠在這個函數中執行一些異步的操做。redux
// 判斷是否是Promise函數
import isPromise from 'is-promise';
// 標準flux action
import { isFSA } from 'flux-standard-action';
export default function promiseMiddleware({ dispatch }) {
return next => action => {
// 首先判斷action是否是flux規定的類型
if (!isFSA(action)) {
return isPromise(action) ? action.then(dispatch) : next(action);
}
return isPromise(action.payload)
? action.payload
.then(result => dispatch({ ...action, payload: result }))
.catch(error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
})
: next(action);
};
}
複製代碼
redux-promise將 promise
貫徹到底。將 promise
做爲 action
傳給 dispatch
,讓中間件處理 resolve
,能夠去少寫 .then().catch()
之類的代碼。 redux-thunk和redux-promise用法實際比較相似,都是觸發一個 function/promise
讓中間件本身決定處理反作用的時機。這能解決大部分的反作用場景,但對於更復雜的反作用狀況,就須要寫大量代碼。而redux-saga正是能很好得解決這個問題。promise
用法和代碼就不作探討了。說說概念吧,redux-saga創造了一個saga層,專門用來處理反作用的。那麼redux-saga是怎麼處理反作用的呢? 首先,用戶行爲產生 action
,派發 reducer
時,saga層監聽到特定的 action
(redux-saga提供一些輔助函數,用來監聽將被派發到 reducer
的 action
)進行處理。處理 action
的函數叫作 Effect
,Effect
是個 Generate
函數,提供的 yield
能夠用來控制代碼執行,因此redux-saga適合處理異步。而且在 Effect
中也能繼續發起一個普通 action
,讓 reducer
處理。 這就是redux-saga的大體執行過程。好了,如今進入本文第二個主題,如何使 dispatch promiseify
。異步
這裏的 dispatch promiseify 指的是在使用redux和redux-saga狀況下去實現的。那麼開始吧。async
function* asyncIncrease(action) {
return action.payload
}
// 省略一些步驟
......
store.dispatch({
type: 'asyncIncrease', payload: 30
}).then(res => {
console.log(res); // 30
})
複製代碼
由於 Effect
是個 Generate
函數,那先來瞧瞧 Generate
的一些概念吧。函數
Generator
函數是一個普通函數,可是有兩個特徵。一是,function
關鍵字與函數名之間有一個星號;二是,函數體內部使用 yield
表達式,定義不一樣的內部狀態Generator
函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象yield
表達式,就暫停執行後面的操做,並將緊跟在 yield
後面的那個表達式的值,做爲返回的對象的 value
屬性值。好了,暫時就這些概念,接下來看看怎麼實現吧。 用戶發起 action
到 reducer
後。由於 reducer
是個純函數,即相同的輸入,永遠會獲得相同的輸出,並且沒有任何可觀察的反作用。因此咱們得區分 action
,是由redux-saga處理仍是reducer處理。因此得有個中間件,而且返回一個 promise
,使 dispatch
可以使用then
方法。
const promiseMiddlware = () => next => action => {
// 經過type來判斷是否是由Effect處理
// 若是是,返回promise
if (isEffect(action.type) {
return new Promise((resolve, reject) => {
next({
resolve,
reject,
...action,
})
})
} else {
return next(action)
}
}
複製代碼
// action其實就包含了resolve,reject和payload
function* asyncIncreate(action) {
const { resolve, payload } = action
resolve(payload)
}
store.dispatch({
type: 'asyncIncrease', payload: 30
}).then(res => {
console.log(res); // 30
})
複製代碼
實現了 dispatch
的 promiseify
,但不以爲每次寫個 Effect
,還須要寫 resolve
,這樣不麻煩嗎?那咱們轉變一下思路,能不能再有個 Effect
專門來包裝實際的 Effect
,外層 Effect
去 resolve
呢?
// 1. 先寫個外層的Effect
function* baseEffect(action) {
const {resolve, reject,...rest} = action
// 2. 經過yield執行實際的Effect
const res = yield asyncIncreate(rest)
resolve(res)
}
// asyncIncreate就返回值就ok了
function* asyncIncreate(action) {
return action.payload
}
// 省略createStore等步驟
store.dispatch({
type: 'asyncIncrease', payload: 30
}).then(res => {
console.log(res); // 30
})
複製代碼
好了,dispatch
的 promiseify
就實現了,是否是挺簡單的,這只是個粗略版本,還能夠更靈活好用,須要深刻了解的,能夠去dva的源碼。
redux是目前最流行的react的狀態管理庫。因爲redux的設計思想,致使反作用是個問題,但也不難解決,有優秀的中間件能夠去解決。在使用redux和redux-saga時,若是想使 dispatch promiseify
呢,能夠去寫個中間件,再搭配一個外層 Effect
就ok了。