redux 中間件入門

前言

React/Redux項目結束後,當我在研究react-router源碼的時候發現當中有一部分含中間件的思想,因此纔想把中間件從新梳理一遍;在以前看redux瞭解到中間件,redux層面中間件的理解對項目前期比較有幫助,雖然項目中後期基本能夠忽略這層概念;如今對這部分的筆記從新梳理,這裏只針對這個中間件作一個理解。react

若是想學習項目的底層建設,建議先去學習官網redux案例,以後在學習react-router的使用ajax

Redux 中間件介紹

Redux 目的是提供第三方插件的模式,改變action -> reducer 的過程。變爲 action -> middlewares -> reducer 。本身在項目中使用它改變數據流,實現異步 action ;下面會對日誌輸出作一個開場。redux

使用 Redux 中間件

Redux 中 applyMiddleware 的方法,能夠應用多箇中間件,這裏先只寫一箇中間件,以日誌輸出中間件爲例後端

//利用中間件作打印log
import {createStore,applyMiddleware} from 'redux';
import logger from '../api/logger';
import rootReducer from '../reducer/rootReducer';


let createStoreWithMiddleware = applyMiddleware(logger)(createStore);
let store = createStoreWithMiddleware(rootReducer);
// 也能夠直接這樣,能夠參考createStore
// createStore(
//     rootReducer,
//     applyMiddleware(logger)
// )
export default store;
logger 中間件結構分析
const logger = store => next => action => {
    let result = next(action); // 返回的也是一樣的action值
    console.log('dispatch', action);
    console.log('nextState', store.getState());
    return result;
};

export default logger;

store => next => action =>{} 實現了三層函數嵌套,最後返回 next ,給下一個中間件使用,接下來把三層函數拆解;api

從applyMiddleware源碼開始分析 redux/src/applyMiddleware.jspromise

export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer, initialState, enhancer) => {
        var store = createStore(reducer, initialState, enhancer)
        var dispatch = store.dispatch
        var chain = []
        var middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
        return {
            ...store,
            dispatch
        }
    }
}

最外層store
//源碼分析react-router

chain = middlewares.map(middleware => middleware(middlewareAPI));

咱們發現store是middlewareAPI,app

//store異步

var middlewareAPI = {
    getState: store.getState,
    dispatch: (action) => dispatch(action)
}

而後就剩下async

next => action => {
    let result = next(action); // 返回的也是一樣的action值
    console.log('dispatch', action);
    console.log('nextState', store.getState());
    return result;
};

中間層next
//源碼分析

dispatch = compose(...chain)(store.dispatch)

先來分析compose(...chain)

//compose源碼

export default function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg
    }

    if (funcs.length === 1) {
        return funcs[0]
    }

    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0, -1)
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

compose利用Array.prototype.reduceRight的方法

//reduceRight遍歷介紹

[0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) {
    return previousValue + currentValue;
}, 10);

//結果 10+4+3+2+1+0 = 20

由於咱們這裏的中間件就只有一個,因此沒有使用到reduceRight直接返回,直接返回func[0](自己);再由compose(...chain)(store.dispatch),咱們能夠知道next就是store.dispatch

(action) => {
    let result = store.dispatch(action); // 這裏的next就是store.dispatch
    console.log('dispatch', action);
    console.log('nextState', store.getState());
    return result;
};

咱們以後調用的dispath就是觸發的是上面這個函數(這裏就單箇中間件);

多箇中間件

經過上面的 applyMiddleware , compose 和中間件的結構,

假設應用了以下的中間件: [A, B, C],這裏咱們使用es5的結構作分析

分析action觸發的完整流程

三個中間件

//A
function A(store) {
    return function A(next) {
        return function A(action) {
            /*...*/;
            next(action);
            /*...*/;
            return /*...*/;
        }
    }
}
//B
function B(store) {
    return function B(next) {
        return function B(action) {
            /*...*/;
            next(action);
            /*...*/;
            return /*...*/;
        }
    }
}
//C
function C(store) {
    return function C(next) {
        return function C(action) {
            /*...*/;
            next(action);
            /*...*/;
            return /*...*/;
        }
    }
}

經過chain = middlewares.map(middleware => middleware(middlewareAPI)),三個中間件的狀態變化

//A
function A(next) {
    return function A(action) {
        /*...*/;
        next(action);
        /*...*/;
        return /*...*/;
    }
}
//B
function B(next) {
    return function B(action) {
        /*...*/;
        next(action);
        /*...*/;
        return /*...*/;
    }
}
//C
function C(next) {
    return function C(action) {
        /*...*/;
        next(action);
        /*...*/;
        return /*...*/;
    }
}

再由dispatch = compose(...chain)(store.dispatch),咱們轉化下

const last = C;
const rest = [A,B]
dispatch = rest.reduceRight(
    (composed, f) =>{
        return f(composed)
    }, 
    last(store.dispatch)
)

咱們獲得的結果

dispatch = A(B(C(store.dispatch)));

進一步分析,咱們獲得的結果

dispatch = A(B(C(store.dispatch)));

//執行C(next),獲得結果

A(B(function C(action) {/*...*/;next(action);/*...*/;return /*...*/;}));

//此時的next = store.dispatch

//繼續執行B(next)

A(function B(action) {/*...*/;next(action);/*...*/;return /*...*/;});

//此時的

next = function C(action) {/*...*/;next(action);/*...*/;return /*...*/;}

//繼續執行A(next)

function A(action) {/*...*/;next(action);/*...*/;return /*...*/;};

//此時的

next = function B(action) {/*...*/;next(action);/*...*/;return /*...*/;}

一個action觸發執行順序,A(action) -> B(action) -> C(action) -> store.dispatch(action)(生產最新的 store 數據);

若是next(action)下面還有須要執行的代碼,繼續執行 C(next 後的代碼)->B(next 後的代碼)->A(next 後的代碼)

總結:

先從內到外生成新的func,而後由外向內執行。原本咱們能夠直接使用store.dispatch(action),可是咱們能夠經過中間件對action作一些處理或轉換,好比異步操做,異步回調後再執行next;這樣的設計很巧妙,只有等待next,才能夠繼續作操做,和平時直接異步回調又有些不同

項目實踐 ->異步

咱們知道redux中actions分爲actionType,actionCreator,而後在由reducer進行修改數據;

官方例子中async直接在actionCreator作了ajax請求;

咱們把ajax放入中間件觸發下面要講的與官方real-world相似

我這邊使用redux-thunk

applyMiddleware(reduxThunk, api)

先來看看redux-thunk的源碼

function createThunkMiddleware(extraArgument) {
    return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {//從新分發
            return action(dispatch, getState, extraArgument);
        }
        return next(action);//傳遞給下一個中間件
    };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

這樣一來咱們能夠把異步寫成一個複用的actionCreator;

import * as types from '../../constants/actions/common';

export function request(apiName, params, opts = {}) {
    return (dispatch, getState) => {
        let action = {
            'API': {
                apiName: apiName,
                params: params,
                opts: opts
            },
            type: types.API_REQUEST
        };
        return dispatch(action);
    };
}

//其餘地方調用複用的方法以下:

export { request } from './request';

正常的寫法,不是異步的,就是以前的寫法

export function cartSelect(id) {
    return { 
        type: types.CART_MAIN_SELECT, 
        id
    };
}

而後就是下一個中間件的處理 api.js

//本身封裝的ajax,能夠使用別的,好比isomorphic-fetch
import net from 'net';
//項目中所有的接口,至關於一個關於異步的actionType有一個對應的後端接口
import API_ROOT from 'apiRoot';

export default store => next => action => {
    let API_OPT = action['API'];

    if (!API_OPT) {
        //咱們約定這個沒聲明,就不是咱們設計的異步action,執行下一個中間件
        return next(action);
    }

    let ACTION_TYPE = action['type'];
    let { apiName, params = {} , opts = {} } = API_OPT;
    /##
     * 若是有傳遞localData,就不會觸發ajax了,直接觸發_success
     * 當前也能夠傳其餘參數
     */
    let { localData } = opts;
    let {
        onSuccess,
        onError,
        onProgress,
        ajaxType = 'GET',
        param
    } = params;
    // 觸發下一個action
    let nextAction = function(type, param, opts) {
        action['type'] = type;
        action['opts'] = opts;
        delete param['onSuccess'];
        delete param['onError'];
        const nextRequestAction = {...action,...param}
        return nextRequestAction;
    };

    params={
        ...params,
        data: null
    };
    // 觸發正在請求的action
    let result = next(nextAction(apiName + '_ON', params, opts));
    net.ajax({
        url: API_ROOT[apiName],
        type: ajaxType,
        param,
        localData,
        success: data => {
            onSuccess && onSuccess(data);
            params={
                ...params,
                data
            };
            //觸發請求成功的action
            return next(nextAction(apiName + '_SUCCESS', params, opts));
        },
        error: data => {
            onError && onError(data);
            //觸發請求失敗的action
            return next(nextAction(apiName + '_ERROR', params, opts));
        }
    });

    return result;
};

強調一點:項目中所有的接口,至關於一個關於異步的actionType有一個對應的後端接口,因此咱們才能夠經過API_ROOT[apiName]找到這個接口

以cart爲列子(下面是對應的每一個文件):

actionType:

//異步

export const CART_MAIN_GET = 'CART_MAIN_GET';
//非異步
export const CART_MAIN_SELECT = 'CART_MAIN_SELECT';
api:

const api = {
    'CART_MAIN_GET':'/shopping-cart/show-shopping-cart'
};
export default api;
APIROOT修改:

import cart from './api/cart';
const APIROOT = {
    ...cart
};
export default API;
actionCreator:

//項目中使用redux的bindActionCreators作一個統一的綁定,因此在這裏單獨引入
export { request } from './request';
//下面是非異步的方法
export function cartSelect(id) {
    return { 
        type: types.CART_MAIN_SELECT, 
        id
    };
}
項目中發起結構是這樣的:

let url = types.CART_MAIN_GET;
let param = {};
let params = {
    param: param,
    ajaxType: 'GET',
    onSuccess: (res) => {
        /*...*/
    },
    onError: (res) => {
        /*...*/
    }
};
request(url, params, {});
其對應的reducers就是下面

import * as types from '../constants/actions/cart';
const initialState = {
    main:{
        isFetching: 0,//是否已經獲取 
        didInvalidate:1,//是否失效
        itemArr:[],//自定義模版
        itemObj:{},//自定義模版數據
        header:{}//頭部導航
    }
};
export default function(state = initialState, action) {
    let newState;
    switch (action.type) {
        case types.HOME_MAIN_GET + '_ON'://能夠不寫
            /*...*/
            return newState;
        case types.HOME_MAIN_GET + '_SUCCESS':
            /*...*/
            return newState;
        case types.HOME_MAIN_GET + '_ERROR'://能夠不寫
            /*...*/
            return newState;
        default:
            return state;
    }
};

異步,數據驗證均可以經過中間件作處理;引用Generator,Async/Await,Promise處理,能夠參考社區中的一些其餘方式,好比:

redux-promise

redux-saga

相關文章
相關標籤/搜索