Redux介绍之异步Action

19 May

上一篇介绍了中间件,是给本篇做铺垫用的,可以帮助你理解本篇介绍的异步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

至此Redux教程已经结束,顺便把项目的目录结构也定了:

redux

你可以根据业务需要再加上apis(统一管理服务器请求),i18n(多国语目录),styles(通用的css样式),template目录(HTML模板。因为教程有多篇,所以我将template目录放到了项目的根目录下)。将目录结构,和通用方法加入你们项目的脚手架里,这样就可以规范react-redux项目的代码。

最后,如果觉得教程还行,请不吝啬Github上star一下,点这里,这个要求不过分吧 ^_^

Leave a Reply

Your email address will not be published. Required fields are marked *