摘要:接下來(lái)的函數(shù)就有點(diǎn)難度了,讓我們一行一行來(lái)看。上面實(shí)際的含義就是將數(shù)組每一個(gè)執(zhí)行的返回值保存的數(shù)組中。需要注意的是,方法返回值并不是數(shù)組,而是形如初始值的經(jīng)過(guò)疊加處理后的操作。從而實(shí)現(xiàn)異步的。
這段時(shí)間都在學(xué)習(xí)Redux,感覺(jué)對(duì)我來(lái)說(shuō)初學(xué)難度很大,中文官方文檔讀了好多遍才大概有點(diǎn)入門(mén)的感覺(jué),小小地總結(jié)一下,首先可以看一下Redux的基本流程:
從上面的圖可以看出,簡(jiǎn)單來(lái)說(shuō),單一的state是存儲(chǔ)在store中,當(dāng)要對(duì)state進(jìn)行更新的時(shí)候,首先要發(fā)起一個(gè)action(通過(guò)dispatch函數(shù)),action的作用就是相當(dāng)于一個(gè)消息通知,用來(lái)描述發(fā)生了什么(比如:增加一個(gè)Todo),然后reducer會(huì)根據(jù)action來(lái)進(jìn)行對(duì)state更新,這樣就可以根據(jù)新的state去渲染View。
當(dāng)然上面僅僅是發(fā)生同步Action的情況下,如果是Action是異步的(例如從服務(wù)器獲取數(shù)據(jù)),那么情況就有所不同了,必須要借助Redux的中間件Middleware。
Redux moddleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer
根據(jù)官方的解釋,Redux中間件在發(fā)起一個(gè)action至action到達(dá)reducer的之間,提供了一個(gè)第三方的擴(kuò)展。本質(zhì)上通過(guò)插件的形式,將原本的action->redux的流程改變?yōu)?b>action->middleware1->middleware2-> ... ->reducer,通過(guò)改變數(shù)據(jù)流,從而實(shí)現(xiàn)例如異步Action、日志輸入的功能。
首先我們舉例不使用中間件Middleware創(chuàng)建store:
import rootReducer from "./reducers" import {createStore} from "redux" let store = createStore(rootReducer);
那么使用中間件的情況下(以redux-logger舉例),創(chuàng)建過(guò)程如下:
import rootReducer from "./reducers" import {createStore,applyMiddleware} from "redux" import createLogger from "redux-logger" const loggerMiddleware = createLogger(); let store = applyMiddleware(loggerMiddleware)(createStore)(rootReducer);
那么我們不經(jīng)要問(wèn)了,為什么采用了上面的代碼就可以實(shí)現(xiàn)打印日志的中間件呢?
首先給出applyMiddleware的源碼(Redux1.0.1版本):
export default function applyMiddleware(...middlewares) { return (next) => (reducer, initialState) => { var store = next(reducer, initialState); 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 }; }; }
上面的代碼雖然只有不到20行,但看懂確實(shí)是不太容易,實(shí)際上包含了函數(shù)式編程各種技術(shù),首先最明顯的使用到了柯里化(Currying),在我理解中柯里化(Currying)實(shí)際就是將多參數(shù)函數(shù)轉(zhuǎn)化為單參數(shù)函數(shù)并延遲執(zhí)行函數(shù),例如:
function add(x){ return function(y){ return x + y; } } var add5 = add(5); console.log(add5(10)); // 10
關(guān)于柯里化(Currying)更詳細(xì)的介紹可以看我之前的一篇文章從一道面試題談?wù)労瘮?shù)柯里化(Currying)。
首先我們看applyMiddleware的總體結(jié)構(gòu):
export default function applyMiddleware(...middlewares) { return (next) => (reducer, initialState) => { }; }
哈哈,典型的柯里化(Currying),其中(...middlewares)用到了ES6中的新特性,用于將任意個(gè)中間件參數(shù)轉(zhuǎn)化為中間件數(shù)組,因此很容易看出來(lái)在該函數(shù)的調(diào)用方法就是:
let store = applyMiddleware(middleware1,middleware2)(createStore)(rootReducer);
其中applyMiddleware形參和實(shí)參的對(duì)應(yīng)關(guān)系是:
形參 | 實(shí)參 |
---|---|
middlewares | [middleware1,middleware2] |
createStore | Redux原生createStore |
reducer, preloadedState, enhancer | 原生createStore需要填入的參數(shù) |
再看函數(shù)體:
var store = next(reducer, initialState); var dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) };
上面代碼非常簡(jiǎn)單,首先得到store,并將之前的store.dispatch存儲(chǔ)在變量dispatch中,聲明chain,以及將middleware需要的參數(shù)存儲(chǔ)到變量middlewareAPI中。接下來(lái)的函數(shù)就有點(diǎn)難度了,讓我們一行一行來(lái)看。
chain = middlewares.map(middleware => middleware(middlewareAPI))
上面實(shí)際的含義就是將middleware數(shù)組每一個(gè)middleware執(zhí)行
middleware(middlewareAPI)的返回值保存的chain數(shù)組中。那么我們不經(jīng)要問(wèn)了,中間件函數(shù)到底是怎樣的?我們提供一個(gè)精簡(jiǎn)版的createLogger函數(shù):
export default function createLogger({ getState }) { return (next) => (action) => { const console = window.console; const prevState = getState(); const returnValue = next(action); const nextState = getState(); const actionType = String(action.type); const message = `action ${actionType}`; console.log(`%c prev state`, `color: #9E9E9E`, prevState); console.log(`%c action`, `color: #03A9F4`, action); console.log(`%c next state`, `color: #4CAF50`, nextState); return returnValue; }; }
可見(jiàn)中間件createLogger也是典型的柯里化(Currying)函數(shù)。{getState}使用了ES6的解構(gòu)賦值,createLogger(middlewareAPI))返回的(也就是數(shù)組chain存儲(chǔ)的是)函數(shù)的結(jié)構(gòu)是:
(next) => (action) => { //包含getState、dispatch函數(shù)的閉包 };
我們接著看我們的applyMiddleware函數(shù)
dispatch = compose(...chain,store.dispatch)
這句是最精妙也是最有難度的地方,注意一下,這里的...操作符是數(shù)組展開(kāi),下面我們先給出Redux中復(fù)合函數(shù)compose函數(shù)的實(shí)現(xiàn)(Redux1.0.1版本):
export default function compose(...funcs) { return funcs.reduceRight((composed, f) => f(composed)); }
首先先明確一下reduceRight(我用過(guò)的次數(shù)區(qū)區(qū)可數(shù),所以介紹一下reduce和reduceRight)
Array.prototype.reduce.reduce(callback, [initialValue])
reduce方法有兩個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)callback,用于針對(duì)數(shù)組項(xiàng)的操作;第二個(gè)參數(shù)則是傳入的初始值,這個(gè)初始值用于單個(gè)數(shù)組項(xiàng)的操作。需要注意的是,reduce方法返回值并不是數(shù)組,而是形如初始值的經(jīng)過(guò)疊加處理后的操作。
callback分別有四個(gè)參數(shù):
accumulator:上一次callback返回的累積值
currentValue: 當(dāng)前值
currentIndex: 當(dāng)前值索引
array: 數(shù)組
例如:
var sum = [0, 1, 2, 3].reduce(function(a, b) {
return a + b;
}, 0);
// sum is 6
reduce和reduceRight的區(qū)別就是從左到右和從右到左的區(qū)別。所以如果我們調(diào)用compose([func1,func2],store.dispatch)的話,實(shí)際返回的函數(shù)是:
//也就是當(dāng)前dispatch的值 func1(func2(store.dispatch))
勝利在望,看最后一句:
return { ...store, dispatch };
這里其實(shí)是ES7的用法,相當(dāng)于ES6中的:
return Object.assign({},store,{dispatch:dispatch});
或者是Underscore.js中的:
return _.extends({}, store, { dispatch: dispatch });
懂了吧,就是新創(chuàng)建的一個(gè)對(duì)象,將store中的所有可枚舉屬性復(fù)制進(jìn)去(淺復(fù)制),并用當(dāng)前的dispatch覆蓋store中的dispatch屬性。所以
let store = applyMiddleware(loggerMiddleware)(createStore)(rootReducer);
中的store中的dispatch屬性已經(jīng)不是之前的Redux原生的dispatch而是類似于func1(func2(store.dispatch))這種形式的函數(shù)了,但是我們不禁又要問(wèn)了,那么中間件Miffffdleware又是怎么做的呢,我們看一下之前我們提供的建議的打印日志的函數(shù):
export default function createLogger({ getState }) { return (next) => (action) => { const console = window.console; const prevState = getState(); const returnValue = next(action); const nextState = getState(); const actionType = String(action.type); const message = `action ${actionType}`; console.log(`%c prev state`, `color: #9E9E9E`, prevState); console.log(`%c action`, `color: #03A9F4`, action); console.log(`%c next state`, `color: #4CAF50`, nextState); return returnValue; }; }
假設(shè)一下,我們現(xiàn)在使用兩個(gè)中間件,createLogger和createMiddleware,其中createMiddleware的函數(shù)為
export default function createMiddleware({ getState }) { return (next) => (action) => { return next(action) }; }
調(diào)用形式為:
let store = applyMiddleware(createLogger,createMiddleware)(createStore)(rootReducer);
如果調(diào)用了store.dispatch(action),chain中的兩個(gè)函數(shù)分別是
createLogger和createMiddleware中的
(next) => (action) => {}
部分。我們姑且命名一下chain中關(guān)于createLogger的函數(shù)叫做
func1,關(guān)于createMiddleware的函數(shù)叫做func2。那么現(xiàn)在調(diào)用
store.dispatch(action),實(shí)際就調(diào)用了(注意順序)
//這里的store.dispatch是原始Redux提供的dispatch函數(shù) func1(func2(store.dispatch))(action)
上面的函數(shù)大家注意之前執(zhí)行次序,首先func2(store.dispatch再是func1(args)(action)。對(duì)于func1獲得的next的實(shí)參是參數(shù)是:
(action)=>{ //func2中的next是store.dispatch next(action); }
那么實(shí)際上func1(...)(action)執(zhí)行的時(shí)候,也就是
const console = window.console; const prevState = getState(); const returnValue = next(action); const nextState = getState(); const actionType = String(action.type); const message = `action ${actionType}`; console.log(`%c prev state`, `color: #9E9E9E`, prevState); console.log(`%c action`, `color: #03A9F4`, action); console.log(`%c next state`, `color: #4CAF50`, nextState); return returnValue;
的時(shí)候,getState調(diào)用的閉包MiddlewareAPI中的Redux的getState函數(shù),調(diào)用next(action)的時(shí)候,會(huì)回調(diào)createMiddleware函數(shù),然后createMiddleware中next函數(shù)會(huì)回調(diào)真正的store.dispatch(action)。因此我們可以看出來(lái)實(shí)際的調(diào)用順序是和傳入中間件順序相反的,例如:
let store = applyMiddleware(Middleware1,Middleware2,Middleware3)(createStore)(rootReducer);
實(shí)際的執(zhí)行是次序是store.dispatch->Middleware3->Middleware2->Middleware1。
不知道大家有沒(méi)有注意到一點(diǎn),
var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) };
并沒(méi)有直接使用dispatch:dispatch,而是使用了dispatch:(action) => dispatch(action),其目的是如果使用了dispatch:dispatch,那么在所有的Middleware中實(shí)際都引用的同一個(gè)dispatch(閉包),如果存在一個(gè)中間件修改了dispatch,就會(huì)導(dǎo)致后面一下一系列的問(wèn)題,但是如果使用dispatch:(action) => dispatch(action)就可以避免這個(gè)問(wèn)題。
接下來(lái)我們看看異步的action如何實(shí)現(xiàn),我們先演示一個(gè)異步action creater函數(shù):
export const FETCHING_DATA = "FETCHING_DATA"; // 拉取狀態(tài) export const RECEIVE_USER_DATA = "RECEIVE_USER_DATA"; //接收到拉取的狀態(tài) export function fetchingData(flag) { return { type: FETCHING_DATA, isFetchingData: flag }; } export function receiveUserData(json) { return { type: RECEIVE_USER_DATA, profile: json } } export function fetchUserInfo(username) { return function (dispatch) { dispatch(fetchingData(true)); return fetch(`https://api.github.com/users/${username}`) .then(response => { console.log(response); return response.json(); }) .then(json => { console.log(json); return json; }) .then((json) => { dispatch(receiveUserData(json)) }) .then(() => dispatch(fetchingData(false))); }; }
上面的代碼用來(lái)從Github API中拉取名為username的用戶信息,可見(jiàn)首先fetchUserInfo函數(shù)會(huì)dispatch一個(gè)表示開(kāi)始拉取的action,然后使用fetch函數(shù)訪問(wèn)Github的API,并返回一個(gè)Promise,等到獲取到數(shù)據(jù)的時(shí)候,dispatch一個(gè)收到數(shù)據(jù)的action,最后dispatch一個(gè)拉取結(jié)束的action。因?yàn)槠胀ǖ?b>action都是一個(gè)純JavaScript Object對(duì)象,但是異步的Action卻返回的是一個(gè)function,這是我們就要使用的一個(gè)中間件:redux-thunk。
我們給出一個(gè)類似redux-thunk的實(shí)現(xiàn):
export default function thunkMiddleware({ dispatch, getState }) { return next => action => typeof action === ‘function’ ? action(dispatch, getState) : next(action); }
這個(gè)和你之前看到的中間件很類似。如果得到的action是個(gè)函數(shù),就用dispatch和getState當(dāng)作參數(shù)來(lái)調(diào)用它,否則就直接分派給store。從而實(shí)現(xiàn)異步的Action。
Redux入門(mén)學(xué)習(xí),如果有寫(xiě)的不對(duì)的地方,希望大家指正,歡迎大家圍觀我的博客:
MrErHu
SegmentFault
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/81486.html
摘要:在函數(shù)式編程中,異步操作修改全局變量等與函數(shù)外部環(huán)境發(fā)生的交互叫做副作用通常認(rèn)為這些操作是邪惡骯臟的,并且也是導(dǎo)致的源頭。 注:這篇是17年1月的文章,搬運(yùn)自本人 blog... https://github.com/BuptStEve/... 零、前言 在上一篇中介紹了 Redux 的各項(xiàng)基礎(chǔ) api。接著一步一步地介紹如何與 React 進(jìn)行結(jié)合,并從引入過(guò)程中遇到的各個(gè)痛點(diǎn)引出 ...
摘要:上一篇文章講解了如何使用,本篇文章將進(jìn)一步深入,從的源碼入手,深入學(xué)習(xí)的中間件機(jī)制。的功能是讓支持異步,讓我們可以在中跟服務(wù)器進(jìn)行交互等操作,而他的實(shí)現(xiàn)。。。 上一篇文章講解了redux如何使用,本篇文章將進(jìn)一步深入,從redux的源碼入手,深入學(xué)習(xí)redux的中間件機(jī)制。在這里我們會(huì)以一個(gè)redux-thunk中間件為例,逐步分解redux的中間機(jī)制如何操作,如何執(zhí)行。 閑話不多說(shuō),...
摘要:個(gè)人博客原文地址萬(wàn)物皆對(duì)象在中除值類型之外,其他的都是對(duì)象,為了說(shuō)明這點(diǎn),我們舉幾個(gè)例子我們可以使用來(lái)做類型判斷除了屬于值類型之外,其他都是對(duì)象。 個(gè)人博客原文地址 萬(wàn)物皆對(duì)象 在JavaScript中除值類型之外,其他的都是對(duì)象,為了說(shuō)明這點(diǎn),我們舉幾個(gè)例子我們可以使用typeof來(lái)做類型判斷 typeof a; // undefined typeof 1; ...
摘要:在得到新的狀態(tài)后,依次調(diào)用所有的監(jiān)聽(tīng)器,通知狀態(tài)的變更。執(zhí)行完后,獲得數(shù)組,它保存的對(duì)象是第二個(gè)箭頭函數(shù)返回的匿名函數(shù)。部分源碼利用這個(gè)屬性,所有子組件均可以拿到這個(gè)屬性。 Redux使用中的幾個(gè)點(diǎn): Redux三大設(shè)計(jì)原則 Create Store Redux middleware combineReducer Provider與Connect Redux流程梳理 Redux設(shè)計(jì)特...
摘要:假設(shè)等于,其中,,是三個(gè)中間件,等于,那么可以簡(jiǎn)化為。最終返回中的方法以及經(jīng)過(guò)中間件包裝處理過(guò)的方法。以此類推,第二個(gè)返回的就是第一個(gè)中間件的形參。根據(jù)這個(gè)的討論,在中間件頂層調(diào)用了,結(jié)果導(dǎo)致無(wú)法執(zhí)行后面的中間件。 redux 主要包含 5 個(gè)方法,分別是: createStore combineReducers bindActionCreators applyMiddleware ...
閱讀 2517·2023-04-25 19:31
閱讀 2267·2021-11-04 16:11
閱讀 2819·2021-10-08 10:05
閱讀 1528·2021-09-30 09:48
閱讀 2327·2019-08-30 15:56
閱讀 2423·2019-08-30 15:56
閱讀 2186·2019-08-30 15:53
閱讀 2280·2019-08-30 15:44