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