先貼一張redux的基本結構圖
原圖來自《UNIDIRECTIONAL USER INTERFACE ARCHITECTURES》html
在這張圖中,咱們能夠很清晰的看到,view中產生action,經過store.dispatch(action)將action交由reducer處理,最終根據處理的結果更新view。
在這個過程當中,action是簡單對象,用於描述一個動做以及對應於該動做的數據。例如:前端
const ADD_TODO = 'ADD_TODO'; // action { type: ADD_TODO, data: 'some data' }
而reducer則是純函數,且是冪等的,即只要傳入參數相同,返回計算獲得的下一個 state 就必定相同。沒有特殊狀況、沒有反作用,沒有 API 請求、沒有變量修改,單純執行計算。react
在擁有了以上基本認知以後,咱們來看下redux究竟是如何工做的。Talk is cheap, show me the code.express
import React from 'react' import { createStore, bindActionCreators } from 'redux' import { connect } from 'react-redux' import ReactDom from 'react-dom' import { Provider } from 'react-redux' function createAction() { return { type: 'ADD_TODO', data: 'some data' } } class App extends React.Component { constructor() { super(); } render() { return ( <div style={{width:'200px', height:'200px',margin:'100px',border:'2px solid black'}}> <div onClick={this.props.actions.createAction.bind(this)}> {"Click Me!"} </div> </div> ); } } function mapStateToProps(state) { return { data: state } } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({createAction}, dispatch) } } var AppApp = connect( mapStateToProps, mapDispatchToProps )(App); function reducer(state, action) { console.log(action); return state; } var store = createStore(reducer); ReactDom.render( <Provider store={store}> <AppApp /> </Provider>, document.getElementById('container') );
這是一個精簡版本的redux demo,每點擊一次「Click Me!」,控制檯會打印一次action。json
因爲篇幅限制,以上代碼未分模塊redux
下面是截圖:
數組
控制檯打印輸出:
promise
從上面代碼中能夠清晰的看出,當用戶點擊「Click Me!」的時候,會當即調用createAction產生一個action,以後redux獲取這個action並調用store.dispatch將這個action丟給reducer進行處理,demo中的reducer僅僅打印了action。
數據從view中流出,經reducer處理後又回到了view。
至此,咱們看到的一切都是跟上面的基本認知是一致的。app
接下來講說異步數據流,這塊也是困擾了我很久,直到最近才搞清楚內在緣由。dom
redux爲咱們作了不少的事情,咱們均可以不用經過顯示的調用dispatch函數就將咱們的action傳遞給reducer。這在前面的demo中就能夠看到。可是至此,redux一直沒有解決異步的問題。試想,若是我在頁面輸入一段內容,而後觸發了一個搜索動做,此時須要向服務端請求數據並將返回的數據展現出來。這是一個很常見的功能,可是涉及到異步請求,剛剛的demo中的方法已經再也不適用了。那麼redux是如何解決異步問題的呢?
沒錯,就是引入middleware。middleware,顧名思義就是中間件。用過express的同窗對中間件應該都很熟悉。其實在redux中,middleware並不只僅用於解決異步的問題,它還能夠作不少其餘的事情,好比記錄日誌、錯誤報告、路由等等。
關於redux middleware的說明在官方文檔中已經有了很是清晰的說明,中文版和英文版都有,這裏就不在贅述,只摘錄一句話,說明以下。
It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
這裏我想說下redux middleware的具體實現,我也正是從源代碼中找到了困擾個人問題的緣由。
先看applyMiddleware(...middlewares)的代碼:
import compose from './compose' 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之後,咱們對傳進來的每個middleware進行以下處理:
var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI))
處理後獲得一個數組保存在chain中。以後將chain傳給compose,並將store.dispatch傳給返回的函數。那麼在這裏面作了什麼呢?咱們再看compose的實現:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } else { const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) } }
compose中的核心動做就是將傳進來的全部函數倒序(reduceRight)進行以下處理:
(composed, f) => f(composed)
咱們知道Array.prototype.reduceRight是從右向左累計計算的,會將上一次的計算結果做爲本次計算的輸入。再看看applyMiddleware中的調用代碼:
dispatch = compose(...chain)(store.dispatch)
compose函數最終返回的函數被做爲了dispatch函數,結合官方文檔和代碼,不可貴出,中間件的定義形式爲:
function middleware({dispatch, getState}) { return function (next) { return function (action) { return next(action); } } } 或 middleware = (dispatch, getState) => next => action => { next(action); }
也就是說,redux的中間件是一個函數,該函數接收dispatch和getState做爲參數,返回一個以dispatch爲參數的函數,這個函數的返回值是接收action爲參數的函數(能夠看作另外一個dispatch函數)。在中間件鏈中,以dispatch爲參數的函數的返回值將做爲下一個中間件(準確的說應該是返回值)的參數,下一個中間件將它的返回值接着往下一個中間件傳遞,最終實現了store.dispatch在中間件間的傳遞。
看了中間件的文檔和代碼以後,我算是搞明白了中間件的原理。以前一直困擾個人問題如今看來實際上是概念問題(此處不提也罷),中間件只關注dispatch函數的傳遞,至於在傳遞的過程當中幹了什麼中間件並不關心。
下面看看經過中間件,咱們如何實現異步調用。這裏就不得不提redux-thunk中間件了。
redux與redux-thunk是同一個做者。
咱們知道,異步調用何時返回前端是沒法控制的。對於redux這條嚴密的數據流來講,如何才能作到異步呢。redux-thunk的基本思想就是經過函數來封裝異步請求,也就是說在actionCreater中返回一個函數,在這個函數中進行異步調用。咱們已經知道,redux中間件只關注dispatch函數的傳遞,並且redux也不關心dispatch函數的返回值,因此只須要讓redux認識這個函數就能夠了。
看了一下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;
這段代碼跟上面咱們看到的中間件沒有太大的差異,惟一一點就是對action作了一下以下判斷:
if (typeof action === 'function') { return action(dispatch, getState, extraArgument); }
也就是說,若是發現actionCreater傳過來的action是一個函數的話,會執行一下這個函數,並以這個函數的返回值做爲返回值。前面已經說過,redux對dispatch函數的返回值不是很關心,所以此處也就無所謂了。
這樣的話,在咱們的actionCreater中,咱們就能夠作任何的異步調用了,而且返回任何值也無所謂,因此咱們可使用promise了:
function actionCreate() { return function (dispatch, getState) { // 返回的函數體內自由實現。。。 Ajax.fetch({xxx}).then(function (json) { dispatch(json); }) } }
經過redux-thunk,咱們將異步的操做融合進了現有的數據流中。
最後還須要注意一點,因爲中間件只關心dispatch的傳遞,並不限制你作其餘的事情,所以咱們最好將redux-thunk放到中間件列表的首位,防止其餘中間件中返回異步請求。
以上是最近一段時間學習和思考的總結。在這期間發現,學習新知識的基礎是要把概念理解清楚,不能一味的看樣例跑demo,不理解概念對demo也只是知其然不知其因此然,很容易陷入一些經過樣例代碼理解出來的錯誤的概念中,後面再糾正就須要花費不少時間和精力了!