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添加相應的附加效果。

相關文章
相關標籤/搜索