上一篇介紹了中間件,是給本篇做鋪墊用的,可以幫助你理解本篇介紹的異步Action。前幾篇所有的Action都是同步Action,即本地數據塞進Action中立即dispatch出去更新state。但現實中很多數據是需要從服務器端取的(常見ajax方式取數據),因此需要異步Action。本篇的源碼已上傳Github,請參照src/reactReduxAsync文件夾。
其實Action是個plan object,不存在同步或者異步的概念。所謂的異步Action,本質上是一系列Action動作:
第一步:先dispatch出請求服務器數據的Action(通常此時state里會設計個loading或fetching的值,讓頁面呈現出loading狀態)
第二步:服務器返回了數據(也可返回異常),將數據塞入Action里,再dispatch出這個Action去更新state。
第一步好實現,正常dispatch一個type為request的Action就行了。第二步也好實現,正常dispatch一個帶服務器端數據的Action就行了。關鍵是如何將第一步和第二步捆綁起來,執行第一步后,進入等待狀態,自動執行第二步。這也是異步Action的關鍵,即redux-thunk中間件。
上一篇介紹過中間件:在Redux里中間件等同于修改Store.dispatch方法,將其變成洋蔥圈式的強化版Store.dispatch方法。redux-thunk中間件的源碼總共15行,直接貼出來:
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
}
閱讀源碼可知,常規的Action creator只能返回一個Action,但有了redux-thunk,你的Action creator還可以返回一個function(dispatch, getState)。函數的參數一看名字就知道是干什么的,不贅述。目的就是將上述第一步發送request的Action和第二步發送取得數據后的Action封裝在里面。
entries/reactReduxAsync.js:先在入口處引入redux-thunk
import thunk from 'redux-thunk';
...
const store = createStore(reducer, compose(
applyMiddleware(thunk, logger),
window.devToolsExtension ? window.devToolsExtension() : (f) => f,
));
actions/fetchData.js:
import fetch from 'isomorphic-fetch';
import * as constant from '../configs/action';
import { sleep } from '../lib/common';
const requestData = () => ({
type: constant.REQUEST_DATA,
});
const receiveData = (data) => ({
type: constant.RECEIVE_DATA,
data: data.msg,
});
const doFetchData = () => async(dispatch) => {
dispatch(requestData());
await sleep(1000); // Just 4 mock
return fetch('./api/fetchSampleData.json')
.then((response) => response.json())
.then((json) => dispatch(receiveData(json)));
};
const canFetchData = (state) => {
return !state.fetchData.fetching;
};
export default {
fetchDataAction: () => (dispatch, getState) => {
if (canFetchData(getState())) {
return dispatch(doFetchData());
}
return Promise.resolve();
},
};
解釋一下,requestData是個常規的發送request請求的Action creator,供第一步用。receiveData是個常規的攜帶數據的Action creator,供第二步用。重點在如何將第一步和第二步打包進fetchDataAction里。
fetchDataAction返回的是react-thunk支持的function(dispatch, getState),而不是一個對象形式的Action。doFetchData里dispatch第一步的request請求的Action,(中間因為數據取太快看不出效果,所以強制讓取數據延遲1秒,sleep(1000)這行代碼請無視),然后向服務器fetch數據,取到數據后dispatch第二步的攜帶數據的Action。
中間插著一個canFetchData方法是為優化用的,當正在請求數據時禁止重復請求數據,防止用戶狂點查詢按鈕,節省服務器開銷,這與本篇內容無關,可以無視。
代碼雖短,但里面信息量卻不少,你需要具備中間件, Promise,thunk的知識,ES6的基本語法知識也不可少。
reducers/fetchData.js:
import * as constant from '../configs/action';
import { createReducer } from '../lib/common';
const initialState = {
fetching: false,
data: null,
};
export default createReducer(initialState, {
[constant.REQUEST_DATA]: (state, action) => {
return {
...state,
fetching: true,
};
},
[constant.RECEIVE_DATA]: (state, action) => {
return {
...state,
fetching: false,
data: action.data,
};
},
});
Reducer里只是單純處理數據,沒什么特別的。需要注意的是,設計了一個fetching變量,當收到第一步request的Action時,將其設為true,觸發頁面的loading組件。當收到第二步更新值的Action時,將其設為false,隱藏頁面的loading組件。
效果如下圖,點擊按鈕后獲取到數據,你可以跟著教程自己嘗試一下:
至此Redux教程已經結束,順便把項目的目錄結構也定了:
最后,如果覺得教程還行,請不吝嗇Github上star一下,點這里,這個要求不過分吧 _