React系列---Redux异步流

使用Redux访问服务器,一样要要解决异步问题。ajax

Redux单向数据流,由action对象开始驱动,每一个action对象被派发到Store以后,被分配给reducer函数,reducer完成数据操做后马上返回,reducer返回的结果又被拿去更新Store上的状态数据,更新状态数据的操做马上会被同步给监听Store状态改变的函数,从而引起React视图组件的更新过程。npm

Redux单向数据流

整个过程都是快马加鞭一路同步执行,根本没有异步操做的机会,那应该在 哪里插入访问服务器的异步操做呢?json

redux-thunk中间件

redux-thunk中间件就是解决redux异步操做的标准方式。redux

npm install redux-thunk --save

异步actoin对象

Redux单向数据流的驱动起点是action对象,Redux异步操做也避免不了从派发一个action对象开始。可是这个action对象比较特殊,咱们叫它“异步action对象”。segmentfault

与普通action对象(包含若干字段,其中type必不可少)不一样的是,“异步action对象”不是一个普通的JavaScript对象,而是一个函数。api

这样一个函数类型的action对象派发出去,因为没有type字段,就没有下一步的reducer什么事了。但reducer又不得不按redux数据流的步骤自动介入进来。因此中间件在此时机站出来,认定这件事非他管不可的话,reducer就得一边凉快去。数组

因此,redux-thunk的工做就是检查action对象是否是函数,若是不是就撤退。而若是是的话,就执行这个函数,并把Store的dispatch函数和getState函数做为参数传递进去。promise

寥寥几行的redux-thunk源代码:服务器

function createThunkMiddleware(extraArgument) {
    return ({ dispatch, getState }) => next => action => {
        if(typeof action == 'function') {
            return action(dispatch, getState, extraArgument);
        }
    };
}

能够很清楚地看到,当actoin为函数时,并无调用next或dispatch方法,而是返回action函数的调用。异步

了解到redux-thunk的原理后,咱们模拟一个天气的异步请求。action creator一般能够这么写:

function getWeather(url, params) {
    return (dispatch, getState) => { // 由中间件负责调用,dispatch和getState也由中间件负责传入
        fetch(url, params)
            .then(result => {
                dispatch({
                    type: 'GET_WEATHER_SUCCESS',
                    payload: result
                });
            })
            .catch(err => {
                dispatch({
                    type: 'GET_WEATHER_ERROR',
                    error: err
                });
            });
    };
}

异步action函数的代码基本都是这样的套路:

export const sampleAsyncAction = () => {
    return (dispatch, getState) => {
        // 在这个函数里能够调用异步函数, 自行决定再合适的时机经过dispatch参数派发新的action对象
    }
};

这就是异步action的工做机理,异步action最终仍是要产生同步actoin的派发,才能触达视图的响应。redux-thunk要作的工做也就不过如此,但由于引入了一次函数执行,而这个函数还能访问到dispatch和getState,就给异步操做带来了可能。

异步action函数中,能够经过ajax发起对服务器的异步请求,当获得结果以后,经过参数dispatch,把成功或失败的结果当作actoin对象再派发出去。这一次派发的是普通action对象,就不会被redux-thunk截获,直接到达reducer,最终驱动Store上状态的改变。

redux-promise中间件

咱们发现,异步请求其实都是利用promise来完成的,那么为何不直接经过抽象promise来解决异步流问题呢?

npm install redux-promise --save

经过源码分析一下它是怎么作的:

import { isFSA } from 'flux-standard-action';

function isPromise(val) {
    return val && typeof val.then === 'function';
}

export default function promiseMiddleware({ dispatch }) {
    return next => action => {
        if(!isFSA(action)) {
            return isPromise(action) ? action.then(dispatch) : next(action);
        }
        
        return isPromise(action.payload) 
            ? action.payload.then(
                result => dispatch({ ...action, payload: result }),
                error => {
                    dispatch({ ...action, payload: error, error: true });
                    return Promise.reject(error);
                }
              )
            : next(action);
    };
}

redux-promise兼容了FSA标准,也就是说将返回的结果保存在payload中。实现过程很是容易理解,即判断action或action.payload是否为promise,若是是,就执行then,返回的结果再发送一次dispatch。

咱们利用ES7的async和await语法,能够简化上述获取天气的异步过程:

const fetchData = (url, params) => fetch(url, params);

async function getWeather(url, params) {
    const result = await fetchData(url, params);
    
    if(result.error) {
        return {
            type: 'GET_WEATHER_ERROR',
            error: result.error
        };
    }
    
    return {
        type: 'GET_WEATHER_SUCCESS',
        payload: result
    };
}

redux-composable-fetch

在实际中,咱们还须要加上loading状态。结合上述讨论的两个开源middleware,咱们彻底能够本身实现一个贴合工程须要的middleware,这里将其命名为redux-composable-fetch。

在理想的状况下,咱们不但愿经过复杂的方法去请求数据,而但愿经过以下形式一并完成在异步请求过程当中的不一样状态:

{
    url: '/api/weather.json',
    params: {
        city: encodeURI(city)
    },
    types: ['GET_WEATHER', 'GET_WEATHER_SUCCESS', 'GET_WEATHER_ERROR']
}

能够看到,异步请求的action格式有别于FSA。它并无使用type属性,而使用了types属性。types实际上是三个普通action type的集合,分别表明请求中、请求成功和请求失败。

在请求middleware中,会对action进行格式检查,若存在url和types属性,则说明这个action是一个用于发送异步请求的action。此外,并非全部请求都能携带参数,所以params是可选的。

当请求middleware识别到这是一个用于发送请求的action后,首先会分发一个新的action,这个action的type就是原action里types数组中的第一个元素,即请求中。分发这个新action的目的在于让store可以同步当前请求的状态,如将loading状态置为true,这样在对应的界面上能够展现一个友好的加载中动画。

而后请求middleware会根据action中的url、params、method等参数发送一个异步请求,并在请求响应后根据结果的成功或失败分别分发请求成功和请求失败的新action。

请求middleware的简化实现以下,咱们能够根据具体的场景对此进行改造:

const fetchMiddleware = store => next => action => {
    if(!action.url || !Array.isArray(action.types)) {
        return next(action);
    }
    
    const [LOADING, SUCCESS, ERROR] = action.types;
    
    next({
        type: LOADING,
        loading: true,
        ...action
    });
    
    fetch(action.url, { params: action.params })
        .then(result => {
            next({
                type: SUCCESS,
                loading: false,
                payload: result
            });
        })
        .catch(err => {
            next({
                type: ERROR,
                loading: false,
                error: err
            });
        });
};

这样咱们一步就完成了异步请求的action。

redux-observable

在Redux中,处理异步action的方法很是多,最标准的作法是使用redux-thunk中间件,通过thunk中间件的处理,一个action被dispatch后能够返回一个函数,这个函数能够用来作其余的事:发起异步请求和dispatch另外更多的action。使用redux-promise比redux-thunk更加易用,复杂度也不高,建立的异步action对象符合FSA标准。

在Redux社区中,负有盛名的还有redux-sage、redux-observable等。

redux-observable,是经过建立epics中间件,为每个dispatch添加相应的附加效果。

相关文章
相关标签/搜索