在淺說Flux開發中,簡單介紹了Flux及其開發方式。Flux能夠說是一個框架,其有自己的 Dispatcher
接口供開發者;也能夠說是一種數據流單向控制的架構設計,圍繞單向數據流的核心,其定義了一套行爲規範,以下圖:html
Redux的設計就繼承了Flux的架構,並將其完善,提供了多個API供開發者調用。藉着react-redux,能夠很好的與React結合,開發組件化程度極高的現代Web應用。本文是筆者近半年使用react+redux組合的一些總結,不當之處,敬請諒解。react
Action是數據從應用傳遞到 store/state 的載體,也是開啓一次完成數據流的開始。git
以添加一個todo的Action爲例:github
{ type:'add_todo', data:'我要去跑步' }
這樣就定義了一個添加一條todo的Action,而後就能經過某個行爲去觸發這個Action,由這個Action攜帶的數據(data)去更新store(state/reducer):redux
store.dispatch({ type:'add_todo', data:'your data' })
type
是一個常量,Action必備一個字段,用於標識該Action的類型。在項目初期,這樣定義Action也能愉快的擼碼,可是隨着項目的複雜度增長,這種方式會讓代碼顯得冗餘,由於若是有多個行爲觸發同一個Action,則這個Action要寫屢次;同時,也會形成代碼結構不清晰。於是,得更改建立Action的方式:segmentfault
const ADD_TODO = 'add_todo'; let addTodo = (data='default data') => { return { type: ADD_TODO, data: data } } //觸發action store.dispatch(addTodo());
更改以後,代碼清晰多了,若是有多個行爲觸發同一個Action,只要調用一下函數 addTodo
就行,並將Action要攜帶的數據傳遞給該函數。相似 addTodo
這樣的函數,稱之爲 Action Creator。Action Creator 的惟一功能就是返回一個Action供 dispatch
進行調用。api
可是,這樣的Action Creator 返回的Action 並非一個標準的Action。在Flux的架構中,一個Action要符合 FSA(Flux Standard Action) 規範,須要知足以下條件:promise
是一個純文本對象架構
只具有 type
、payload
、error
和 meta
中的一個或者多個屬性。type
字段不可缺省,其它字段可缺省app
若 Action 報錯,error
字段不可缺省,切必須爲 true
payload
是一個對象,用做Action攜帶數據的載體。因此,上述的寫法能夠更改成:
let addTodo = (data='default data') => { return { type: ADD_TODO, payload: { data } } }
在 redux 全家桶中,能夠利用 redux-actions 來建立符合 FSA 規範的Action:
import {creatAction} from 'redux-actions'; let addTodo = creatAction(ADD_TODO) //same as let addTodo = creatAction(ADD_TODO,data=>data)
能夠採用以下一個簡單的方式檢驗一個Action是否符合FSA標準:
let isFSA = Object.keys(action).every((item)=>{ return ['payload','type','error','meta'].indexOf(item) > -1 })
在我看來,Redux提升了兩個很是重要的功能,一是 Reducer 拆分,二是中間件。Reducer 拆分可使組件獲取其最小屬性(state),而不須要整個Store。中間件則能夠在 Action Creator 返回最終可供 dispatch 調用的 action 以前處理各類事情,如異步API調用、日誌記錄等,是擴展 Redux 功能的一種推薦方式。
Redux 提供了 applyMiddleware(...middlewares)
來將中間件應用到 createStore。applyMiddleware 會返回一個函數,該函數接收原來的 creatStore 做爲參數,返回一個應用了 middlewares 的加強後的 creatStore。
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { //接收createStore參數 var store = createStore(reducer, preloadedState, 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) //返回經middlewares加強後的createStore return { ...store, dispatch } } }
建立 store 的方式也會因是否使用中間件而略有區別。未應用中間價以前,建立 store 的方式以下:
import {createStore} from 'redux'; import reducers from './reducers/index'; export let store = createStore(reducers);
應用中間價以後,建立 store 的方式以下:
import {createStore,applyMiddleware} from 'redux'; import reducers from './reducers/index'; let createStoreWithMiddleware = applyMiddleware(...middleware)(createStore); export let store = createStoreWithMiddleware(reducers);
那麼怎麼自定義一箇中間件呢?
根據 redux 文檔,中間件的簽名以下:
({ getState, dispatch }) => next => action
根據上文的 applyMiddleware
源碼,每一箇中間件接收 getState & dispatch 做爲參數,並返回一個函數,該函數會被傳入下一個中間件的 dispatch 方法,並返回一個接收 action 的新函數。
以一個打印 dispatch action 先後的 state 爲例,建立一箇中間件示例:
export default function({getState,dispatch}) { return (next) => (action) => { console.log('pre state', getState()); // 調用 middleware 鏈中下一個 middleware 的 dispatch。 next(action); console.log('after dispatch', getState()); } }
在建立 store 的文件中調用該中間件:
import {createStore,applyMiddleware} from 'redux'; import reducers from './reducers/index'; import log from '../lib/log'; //export let store = createStore(reducers); //應用中間件log let createStoreWithLog = applyMiddleware(log)(createStore); export let store = createStoreWithLog(reducers);
能夠在控制檯看到輸出:
能夠對 store 應用多箇中間件:
import log from '../lib/log'; import log2 from '../lib/log2'; let createStoreWithLog = applyMiddleware(log,log2)(createStore); export let store = createStoreWithLog(reducers);
log2 也是一個簡單的輸出:
export default function({getState,dispatch}) { return (next) => (action) => { console.log('我是第二個中間件1'); next(action); console.log('我是第二個中間件2'); } }
看控制檯的輸出:
應用多箇中間件時,中間件調用鏈中任何一個缺乏 next(action)
的調用,都會致使 action 執行失敗
Redux 自己不處理異步行爲,須要依賴中間件。結合 redux-actions 使用,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 creator 須要返回一個函數給 redux-thunk 進行調用,示例以下:
export let addTodoWithThunk = (val) => async (dispatch, getState)=>{ //請求以前的一些處理 let value = await Promise.resolve(val + ' thunk'); dispatch({ type:CONSTANT.ADD_TO_DO_THUNK, payload:{ value } }); };
效果以下:
這裏之因此不用 createAction,如前文所說,由於 createAction 會返回一個 FSA 規範的 action,該 action 會是一個對象,而不是一個 function:
{ type: "add_to_do_thunk", payload: function(){} }
若是要使用 createAction,則要自定義一個異步中間件。
export let addTodoWithCustom = createAction(CONSTANT.ADD_TO_DO_CUSTOM, (val) => async (dispatch, getState)=>{ let value = await Promise.resolve(val + ' custom'); return { value }; });
在通過中間件處理時,先判斷 action.payload 是不是一個函數,是則執行函數,不然交給 next 處理:
if(typeof action.payload === 'function'){ let res = action.payload(dispatch, getState); } else { next(action); }
而 async 函數返回一個 Promise,於是須要做進一步處理:
res.then( (result) => { dispatch({...action, payload: result}); }, (error) => { dispatch({...action, payload: error, error: true}); } );
這樣就自定義了一個異步中間件,效果以下:
固然,咱們能夠對函數執行後的結果是不是Promise做一個判斷:
function isPromise (val) { return val && typeof val.then === 'function'; } //對執行結果是不是Promise if (isPromise(res)){ //處理 } else { dispatch({...action, payload: res}); }
那麼,怎麼利用 redux-promise 呢?redux-promise 是能處理符合 FSA 規範的 action 的,其對異步處理的關鍵源碼以下:
action.payload.then( result => dispatch({ ...action, payload: result }), error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); } )
於是,返回的 payload 再也不是一個函數,而是一個 Promise。而 async 函數執行後就是返回一個 Promise,因此,讓上文定義的 async 函數自執行一次就能夠:
export let addTodoWithPromise = createAction(CONSTANT.ADD_TO_DO_PROMISE, (val) => (async (dispatch, getState)=>{ let value = await Promise.resolve(val + ' promise'); return { value }; })() );
結果以下圖:
示例源碼:redux-demo