摘要:接下來的函數(shù)就有點難度了,讓我們一行一行來看。上面實際的含義就是將數(shù)組每一個執(zhí)行的返回值保存的數(shù)組中。需要注意的是,方法返回值并不是數(shù)組,而是形如初始值的經(jīng)過疊加處理后的操作。從而實現(xiàn)異步的。
這段時間都在學(xué)習(xí)Redux,感覺對我來說初學(xué)難度很大,中文官方文檔讀了好多遍才大概有點入門的感覺,小小地總結(jié)一下,首先可以看一下Redux的基本流程:
從上面的圖可以看出,簡單來說,單一的state是存儲在store中,當要對state進行更新的時候,首先要發(fā)起一個action(通過dispatch函數(shù)),action的作用就是相當于一個消息通知,用來描述發(fā)生了什么(比如:增加一個Todo),然后reducer會根據(jù)action來進行對state更新,這樣就可以根據(jù)新的state去渲染View。
當然上面僅僅是發(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ā)起一個action至action到達reducer的之間,提供了一個第三方的擴展。本質(zhì)上通過插件的形式,將原本的action->redux的流程改變?yōu)?b>action->middleware1->middleware2-> ... ->reducer,通過改變數(shù)據(jù)流,從而實現(xiàn)例如異步Action、日志輸入的功能。
首先我們舉例不使用中間件Middleware創(chuàng)建store:
import rootReducer from "./reducers" import {createStore} from "redux" let store = createStore(rootReducer);
那么使用中間件的情況下(以redux-logger舉例),創(chuàng)建過程如下:
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)要問了,為什么采用了上面的代碼就可以實現(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ù),首先最明顯的使用到了柯里化(Currying),在我理解中柯里化(Currying)實際就是將多參數(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)更詳細的介紹可以看我之前的一篇文章從一道面試題談?wù)労瘮?shù)柯里化(Currying)。
首先我們看applyMiddleware的總體結(jié)構(gòu):
export default function applyMiddleware(...middlewares) { return (next) => (reducer, initialState) => { }; }
哈哈,典型的柯里化(Currying),其中(...middlewares)用到了ES6中的新特性,用于將任意個中間件參數(shù)轉(zhuǎn)化為中間件數(shù)組,因此很容易看出來在該函數(shù)的調(diào)用方法就是:
let store = applyMiddleware(middleware1,middleware2)(createStore)(rootReducer);
其中applyMiddleware形參和實參的對應(yīng)關(guān)系是:
形參 | 實參 |
---|---|
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) };
上面代碼非常簡單,首先得到store,并將之前的store.dispatch存儲在變量dispatch中,聲明chain,以及將middleware需要的參數(shù)存儲到變量middlewareAPI中。接下來的函數(shù)就有點難度了,讓我們一行一行來看。
chain = middlewares.map(middleware => middleware(middlewareAPI))
上面實際的含義就是將middleware數(shù)組每一個middleware執(zhí)行
middleware(middlewareAPI)的返回值保存的chain數(shù)組中。那么我們不經(jīng)要問了,中間件函數(shù)到底是怎樣的?我們提供一個精簡版的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; }; }
可見中間件createLogger也是典型的柯里化(Currying)函數(shù)。{getState}使用了ES6的解構(gòu)賦值,createLogger(middlewareAPI))返回的(也就是數(shù)組chain存儲的是)函數(shù)的結(jié)構(gòu)是:
(next) => (action) => { //包含getState、dispatch函數(shù)的閉包 };
我們接著看我們的applyMiddleware函數(shù)
dispatch = compose(...chain,store.dispatch)
這句是最精妙也是最有難度的地方,注意一下,這里的...操作符是數(shù)組展開,下面我們先給出Redux中復(fù)合函數(shù)compose函數(shù)的實現(xiàn)(Redux1.0.1版本):
export default function compose(...funcs) { return funcs.reduceRight((composed, f) => f(composed)); }
首先先明確一下reduceRight(我用過的次數(shù)區(qū)區(qū)可數(shù),所以介紹一下reduce和reduceRight)
Array.prototype.reduce.reduce(callback, [initialValue])
reduce方法有兩個參數(shù),第一個參數(shù)是一個callback,用于針對數(shù)組項的操作;第二個參數(shù)則是傳入的初始值,這個初始值用于單個數(shù)組項的操作。需要注意的是,reduce方法返回值并不是數(shù)組,而是形如初始值的經(jīng)過疊加處理后的操作。
callback分別有四個參數(shù):
accumulator:上一次callback返回的累積值
currentValue: 當前值
currentIndex: 當前值索引
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ù)是:
//也就是當前dispatch的值 func1(func2(store.dispatch))
勝利在望,看最后一句:
return { ...store, dispatch };
這里其實是ES7的用法,相當于ES6中的:
return Object.assign({},store,{dispatch:dispatch});
或者是Underscore.js中的:
return _.extends({}, store, { dispatch: dispatch });
懂了吧,就是新創(chuàng)建的一個對象,將store中的所有可枚舉屬性復(fù)制進去(淺復(fù)制),并用當前的dispatch覆蓋store中的dispatch屬性。所以
let store = applyMiddleware(loggerMiddleware)(createStore)(rootReducer);
中的store中的dispatch屬性已經(jīng)不是之前的Redux原生的dispatch而是類似于func1(func2(store.dispatch))這種形式的函數(shù)了,但是我們不禁又要問了,那么中間件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)在使用兩個中間件,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中的兩個函數(shù)分別是
createLogger和createMiddleware中的
(next) => (action) => {}
部分。我們姑且命名一下chain中關(guān)于createLogger的函數(shù)叫做
func1,關(guān)于createMiddleware的函數(shù)叫做func2。那么現(xiàn)在調(diào)用
store.dispatch(action),實際就調(diào)用了(注意順序)
//這里的store.dispatch是原始Redux提供的dispatch函數(shù) func1(func2(store.dispatch))(action)
上面的函數(shù)大家注意之前執(zhí)行次序,首先func2(store.dispatch再是func1(args)(action)。對于func1獲得的next的實參是參數(shù)是:
(action)=>{ //func2中的next是store.dispatch next(action); }
那么實際上func1(...)(action)執(zhí)行的時候,也就是
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;
的時候,getState調(diào)用的閉包MiddlewareAPI中的Redux的getState函數(shù),調(diào)用next(action)的時候,會回調(diào)createMiddleware函數(shù),然后createMiddleware中next函數(shù)會回調(diào)真正的store.dispatch(action)。因此我們可以看出來實際的調(diào)用順序是和傳入中間件順序相反的,例如:
let store = applyMiddleware(Middleware1,Middleware2,Middleware3)(createStore)(rootReducer);
實際的執(zhí)行是次序是store.dispatch->Middleware3->Middleware2->Middleware1。
不知道大家有沒有注意到一點,
var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) };
并沒有直接使用dispatch:dispatch,而是使用了dispatch:(action) => dispatch(action),其目的是如果使用了dispatch:dispatch,那么在所有的Middleware中實際都引用的同一個dispatch(閉包),如果存在一個中間件修改了dispatch,就會導(dǎo)致后面一下一系列的問題,但是如果使用dispatch:(action) => dispatch(action)就可以避免這個問題。
接下來我們看看異步的action如何實現(xiàn),我們先演示一個異步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))); }; }
上面的代碼用來從Github API中拉取名為username的用戶信息,可見首先fetchUserInfo函數(shù)會dispatch一個表示開始拉取的action,然后使用fetch函數(shù)訪問Github的API,并返回一個Promise,等到獲取到數(shù)據(jù)的時候,dispatch一個收到數(shù)據(jù)的action,最后dispatch一個拉取結(jié)束的action。因為普通的action都是一個純JavaScript Object對象,但是異步的Action卻返回的是一個function,這是我們就要使用的一個中間件:redux-thunk。
我們給出一個類似redux-thunk的實現(xiàn):
export default function thunkMiddleware({ dispatch, getState }) { return next => action => typeof action === ‘function’ ? action(dispatch, getState) : next(action); }
這個和你之前看到的中間件很類似。如果得到的action是個函數(shù),就用dispatch和getState當作參數(shù)來調(diào)用它,否則就直接分派給store。從而實現(xiàn)異步的Action。
Redux入門學(xué)習(xí),如果有寫的不對的地方,希望大家指正,歡迎大家圍觀我的博客:
MrErHu
SegmentFault
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/81486.html
摘要:在函數(shù)式編程中,異步操作修改全局變量等與函數(shù)外部環(huán)境發(fā)生的交互叫做副作用通常認為這些操作是邪惡骯臟的,并且也是導(dǎo)致的源頭。 注:這篇是17年1月的文章,搬運自本人 blog... https://github.com/BuptStEve/... 零、前言 在上一篇中介紹了 Redux 的各項基礎(chǔ) api。接著一步一步地介紹如何與 React 進行結(jié)合,并從引入過程中遇到的各個痛點引出 ...
摘要:個人博客原文地址萬物皆對象在中除值類型之外,其他的都是對象,為了說明這點,我們舉幾個例子我們可以使用來做類型判斷除了屬于值類型之外,其他都是對象。 個人博客原文地址 萬物皆對象 在JavaScript中除值類型之外,其他的都是對象,為了說明這點,我們舉幾個例子我們可以使用typeof來做類型判斷 typeof a; // undefined typeof 1; ...
摘要:假設(shè)等于,其中,,是三個中間件,等于,那么可以簡化為。最終返回中的方法以及經(jīng)過中間件包裝處理過的方法。以此類推,第二個返回的就是第一個中間件的形參。根據(jù)這個的討論,在中間件頂層調(diào)用了,結(jié)果導(dǎo)致無法執(zhí)行后面的中間件。 redux 主要包含 5 個方法,分別是: createStore combineReducers bindActionCreators applyMiddleware ...
閱讀 2520·2023-04-25 19:31
閱讀 2270·2021-11-04 16:11
閱讀 2822·2021-10-08 10:05
閱讀 1530·2021-09-30 09:48
閱讀 2328·2019-08-30 15:56
閱讀 2426·2019-08-30 15:56
閱讀 2189·2019-08-30 15:53
閱讀 2284·2019-08-30 15:44