Redux介紹之異步Action

上一篇介紹了中間件,是給本篇做鋪墊用的,可以幫助你理解本篇介紹的異步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方法是為優化用的,當正在請求數據時禁止重復請求數據,防止用戶狂點查詢按鈕,節省服務器開銷,這與本篇內容無關,可以無視。

代碼雖短,但里面信息量卻不少,你需要具備中間件Promisethunk的知識,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教程已經結束,順便把項目的目錄結構也定了:


你可以根據業務需要再加上apis(統一管理服務器請求),i18n(多國語目錄),styles(通用的css樣式),template目錄(HTML模板。因為教程有多篇,所以我將template目錄放到了項目的根目錄下)。將目錄結構,和通用方法加入你們項目的腳手架里,這樣就可以規范react-redux項目的代碼。

最后,如果覺得教程還行,請不吝嗇Github上star一下,點這里,這個要求不過分吧 _

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容