成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

React 項(xiàng)目中Redux 中間件的理解

amc / 784人閱讀

摘要:如果想學(xué)習(xí)項(xiàng)目的底層建設(shè),建議先去學(xué)習(xí)官網(wǎng)案例,之后在學(xué)習(xí)的使用中間件介紹目的是提供第三方插件的模式,改變的過(guò)程。

前言

React/Redux項(xiàng)目結(jié)束后,當(dāng)我在研究react-router源碼的時(shí)候發(fā)現(xiàn)當(dāng)中有一部分含中間件的思想,所以才想把中間件重新梳理一遍;在之前看redux了解到中間件,redux層面中間件的理解對(duì)項(xiàng)目前期比較有幫助,雖然項(xiàng)目中后期基本可以忽略這層概念;現(xiàn)在對(duì)這部分的筆記重新梳理,這里只針對(duì)這個(gè)中間件做一個(gè)理解。

如果想學(xué)習(xí)項(xiàng)目的底層建設(shè),建議先去學(xué)習(xí)官網(wǎng)redux案例,之后在學(xué)習(xí)react-router的使用

Redux 中間件介紹

Redux 目的是提供第三方插件的模式,改變action -> reducer 的過(guò)程。變?yōu)?action -> middlewares -> reducer 。自己在項(xiàng)目中使用它改變數(shù)據(jù)流,實(shí)現(xiàn)異步 action ;下面會(huì)對(duì)日志輸出做一個(gè)開(kāi)場(chǎng)。

使用 Redux 中間件

Redux 中 applyMiddleware 的方法,可以應(yīng)用多個(gè)中間件,這里先只寫(xiě)一個(gè)中間件,以日志輸出中間件為例

//利用中間件做打印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 中間件結(jié)構(gòu)分析
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 =>{} 實(shí)現(xiàn)了三層函數(shù)嵌套,最后返回 next ,給下一個(gè)中間件使用,接下來(lái)把三層函數(shù)拆解;

從applyMiddleware源碼開(kāi)始分析
///redux/src/applyMiddleware.js
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
//源碼分析
chain = middlewares.map(middleware => middleware(middlewareAPI));

我們發(fā)現(xiàn)store是middlewareAPI,

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

然后就剩下

next => action => {
    let result = next(action); // 返回的也是同樣的action值
    console.log("dispatch", action);
    console.log("nextState", store.getState());
    return result;
};
中間層next
//源碼分析
dispatch = compose(...chain)(store.dispatch)

先來(lái)分析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);

//結(jié)果 10+4+3+2+1+0 = 20

因?yàn)槲覀冞@里的中間件就只有一個(gè),所以沒(méi)有使用到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;
};

我們之后調(diào)用的dispath就是觸發(fā)的是上面這個(gè)函數(shù)(這里就單個(gè)中間件);

多個(gè)中間件

通過(guò)上面的 applyMiddleware , compose 和中間件的結(jié)構(gòu),

假設(shè)應(yīng)用了如下的中間件: [A, B, C],這里我們使用es5的結(jié)構(gòu)做分析

分析action觸發(fā)的完整流程

三個(gè)中間件

//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 /*...*/;
        }
    }
}

通過(guò)chain = middlewares.map(middleware => middleware(middlewareAPI)),三個(gè)中間件的狀態(tài)變化

//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),我們轉(zhuǎn)化下

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

我們得到的結(jié)果

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

進(jìn)一步分析,我們得到的結(jié)果

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

//執(zhí)行C(next),得到結(jié)果

A(B(function C(action) {/*...*/;next(action);/*...*/;return /*...*/;})); 
//此時(shí)的next = store.dispatch

//繼續(xù)執(zhí)行B(next)
A(function B(action) {/*...*/;next(action);/*...*/;return /*...*/;});    
//此時(shí)的next = function C(action) {/*...*/;next(action);/*...*/;return /*...*/;}

//繼續(xù)執(zhí)行A(next)
function A(action) {/*...*/;next(action);/*...*/;return /*...*/;};
//此時(shí)的next = function B(action) {/*...*/;next(action);/*...*/;return /*...*/;}

一個(gè)action觸發(fā)執(zhí)行順序,A(action) -> B(action) -> C(action) -> store.dispatch(action)(生產(chǎn)最新的 store 數(shù)據(jù));

如果next(action)下面還有需要執(zhí)行的代碼,繼續(xù)執(zhí)行 C(next 后的代碼)->B(next 后的代碼)->A(next 后的代碼)

總結(jié):先從內(nèi)到外生成新的func,然后由外向內(nèi)執(zhí)行。本來(lái)我們可以直接使用store.dispatch(action),但是我們可以通過(guò)中間件對(duì)action做一些處理或轉(zhuǎn)換,比如異步操作,異步回調(diào)后再執(zhí)行next;這樣的設(shè)計(jì)很巧妙,只有等待next,才可以繼續(xù)做操作,和平時(shí)直接異步回調(diào)又有些不一樣

項(xiàng)目實(shí)踐 ->異步

我們知道redux中actions分為actionType,actionCreator,然后在由reducer進(jìn)行修改數(shù)據(jù);

官方例子中async直接在actionCreator做了ajax請(qǐng)求;

我們把a(bǔ)jax放入中間件觸發(fā)下面要講的與官方real-world類(lèi)似

我這邊使用redux-thunk

applyMiddleware(reduxThunk, api)

先來(lái)看看redux-thunk的源碼

function createThunkMiddleware(extraArgument) {
    return ({ dispatch, getState }) => next => action => {
        if (typeof action === "function") {//重新分發(fā)
            return action(dispatch, getState, extraArgument);
        }
        return next(action);//傳遞給下一個(gè)中間件
    };
}

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

export default thunk;

這樣一來(lái)我們可以把異步寫(xiě)成一個(gè)復(fù)用的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);
    };
}


//其他地方調(diào)用復(fù)用的方法如下:
export { request } from "./request";

正常的寫(xiě)法,不是異步的,就是之前的寫(xiě)法

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

然后就是下一個(gè)中間件的處理 api.js

//自己封裝的ajax,可以使用別的,比如isomorphic-fetch
import net from "net";
//項(xiàng)目中全部的接口,相當(dāng)于一個(gè)關(guān)于異步的actionType有一個(gè)對(duì)應(yīng)的后端接口
import API_ROOT from "apiRoot";

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

    if (!API_OPT) {
        //我們約定這個(gè)沒(méi)聲明,就不是我們?cè)O(shè)計(jì)的異步action,執(zhí)行下一個(gè)中間件
        return next(action);
    }

    let ACTION_TYPE = action["type"];
    let { apiName, params = {} , opts = {} } = API_OPT;
    /**
     * 如果有傳遞localData,就不會(huì)觸發(fā)ajax了,直接觸發(fā)_success
     * 當(dāng)前也可以傳其他參數(shù)
     */
    let { localData } = opts;
    let {
        onSuccess,
        onError,
        onProgress,
        ajaxType = "GET",
        param
    } = params;
    // 觸發(fā)下一個(gè)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
    };
    // 觸發(fā)正在請(qǐng)求的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
            };
            //觸發(fā)請(qǐng)求成功的action
            return next(nextAction(apiName + "_SUCCESS", params, opts));
        },
        error: data => {
            onError && onError(data);
            //觸發(fā)請(qǐng)求失敗的action
            return next(nextAction(apiName + "_ERROR", params, opts));
        }
    });

    return result;
};

強(qiáng)調(diào)一點(diǎn):項(xiàng)目中全部的接口,相當(dāng)于一個(gè)關(guān)于異步的actionType有一個(gè)對(duì)應(yīng)的后端接口,所以我們才可以通過(guò)API_ROOT[apiName]找到這個(gè)接口

以cart為列子(下面是對(duì)應(yīng)的每個(gè)文件):

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:

//項(xiàng)目中使用redux的bindActionCreators做一個(gè)統(tǒng)一的綁定,所以在這里多帶帶引入
export { request } from "./request";
//下面是非異步的方法
export function cartSelect(id) {
    return { 
        type: types.CART_MAIN_SELECT, 
        id
    };
}

項(xiàng)目中發(fā)起結(jié)構(gòu)是這樣的:

let url = types.CART_MAIN_GET;
let param = {};
let params = {
    param: param,
    ajaxType: "GET",
    onSuccess: (res) => {
        /*...*/
    },
    onError: (res) => {
        /*...*/
    }
};
request(url, params, {});

其對(duì)應(yīng)的reducers就是下面

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

異步,數(shù)據(jù)驗(yàn)證都可以通過(guò)中間件做處理;引用Generator,Async/Await,Promise處理,可以參考社區(qū)中的一些其他方式,比如:

redux-promise

redux-saga

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/86545.html

相關(guān)文章

  • React 328道最全面試題(持續(xù)更新)

    摘要:希望大家在這浮夸的前端圈里,保持冷靜,堅(jiān)持每天花分鐘來(lái)學(xué)習(xí)與思考。 今天的React題沒(méi)有太多的故事…… 半個(gè)月前出了248個(gè)Vue的知識(shí)點(diǎn),受到很多朋友的關(guān)注,都強(qiáng)烈要求再出多些React相前的面試題,受到大家的邀請(qǐng),我又找了20多個(gè)React的使用者,他們給出了328道React的面試題,由我整理好發(fā)給大家,同時(shí)發(fā)布在了前端面試每日3+1的React專(zhuān)題,希望對(duì)大家有所幫助,同時(shí)大...

    kumfo 評(píng)論0 收藏0
  • React專(zhuān)題:react,redux以及react-redux常見(jiàn)一些面試題

    摘要:我們可以為元素添加屬性然后在回調(diào)函數(shù)中接受該元素在樹(shù)中的句柄,該值會(huì)作為回調(diào)函數(shù)的第一個(gè)參數(shù)返回。使用最常見(jiàn)的用法就是傳入一個(gè)對(duì)象。單向數(shù)據(jù)流,比較有序,有便于管理,它隨著視圖庫(kù)的開(kāi)發(fā)而被概念化。 面試中問(wèn)框架,經(jīng)常會(huì)問(wèn)到一些原理性的東西,明明一直在用,也知道怎么用, 但面試時(shí)卻答不上來(lái),也是挺尷尬的,就干脆把react相關(guān)的問(wèn)題查了下資料,再按自己的理解整理了下這些答案。 reac...

    darcrand 評(píng)論0 收藏0
  • 高級(jí)前端面試題大匯總(只有試題,沒(méi)有答案)

    摘要:面試題來(lái)源于網(wǎng)絡(luò),看一下高級(jí)前端的面試題,可以知道自己和高級(jí)前端的差距。 面試題來(lái)源于網(wǎng)絡(luò),看一下高級(jí)前端的面試題,可以知道自己和高級(jí)前端的差距。有些面試題會(huì)重復(fù)。 使用過(guò)的koa2中間件 koa-body原理 介紹自己寫(xiě)過(guò)的中間件 有沒(méi)有涉及到Cluster 介紹pm2 master掛了的話(huà)pm2怎么處理 如何和MySQL進(jìn)行通信 React聲明周期及自己的理解 如何...

    kviccn 評(píng)論0 收藏0
  • React Redux 間件思想遇見(jiàn) Web Worker 靈感(附demo)

    摘要:寫(xiě)在最前原文首發(fā)于作者的知乎專(zhuān)欄中間件思想遇見(jiàn)的靈感附,感興趣的同學(xué)可以知乎關(guān)注,進(jìn)行交流。其中,最重要的一個(gè)便是對(duì)多線(xiàn)程的支持。在中提出了工作線(xiàn)程的概念,并且規(guī)范出的三大主要特征能夠長(zhǎng)時(shí)間運(yùn)行響應(yīng)理想的啟動(dòng)性能以及理想的內(nèi)存消耗。 寫(xiě)在最前 原文首發(fā)于作者的知乎專(zhuān)欄:React Redux 中間件思想遇見(jiàn) Web Worker 的靈感(附demo),感興趣的同學(xué)可以知乎關(guān)注,進(jìn)行交流...

    whatsns 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<