圖解Redux資料流(二)
圖解Redux資料流(二)
Nov 28, 2016
上一篇文章回答了前兩個問題,而這一篇則回答最後一個問題。
Redux如何使用?
前一篇文章描述了Redux的基本要素,並且梳理了一下Redux的資料流動圖
首先,我們的View上體現出的任何操作或者事件都會呼叫Dispatch方法觸發Action
Action 會告知了type和data
然後,Store自動呼叫Reducer,並且傳入兩個引數:當前 State和收到的Action。 Reducer會返回新的State 。並且這個過程是純函式式的,返回的結果由入參決定,不會隨著外界的變化而改變。
知道概念了,那麼我們從程式碼層面如何搭建出Redux資料流的架構呢?
最基本的Redux資料流
import { createStore } from 'redux'; const initialState = { items: [] }; const reducer = (state = initialState, action = {}) => { const { type } = action; switch (type) { case "ADD_ITEM": return Object.assign({}, state, { items: state.items.concat(action.item) }); break; case "GET_ITEM_LIST": return Object.assign({}, state, { items: action.items }); break; } } const store = createStore(reducer) store.subscribe(() => { console.log(store.getState()) }) store.dispatch({type: 'GET_ITEM_LIST','items':[{id:1,name:'test'}]}) store.dispatch({type: 'ADD_ITEM','item':[{id:2,name:'test2'}]}) |
這個程式碼段是最簡單的Redux資料流例子,把建立Store、ActionCreator、Reducer全放在了一個檔案中,在實際專案中,這當然不可取。
其實每個Redux的實體概念都可以拆解成一個單獨的檔案去管理。
在專案中為了專案的規範化,我們可以對所有的action_type常量進行統一的管理,放到單獨的檔案中
const GET_ITEM_LIST = 'GET_ITEM_LIST' const ADD_ITEM = 'ADD_ITEM' |
當然,也許最需要進行拆分是Reducer,在有些規模的實際專案中,state往往比較龐大。對於Reducer而言,我們最好根據相應的業務需求拆分出不同Reducer來管理。
import { createStore,combineReducers } from 'redux'; const itemReducer = (state = {}, action = {}) => { const { type } = action; switch (type) { case "ADD_ITEM": return { ...state, items: state.items.concat(action.item) } break; case "DELETE_ITEM": return { ...state, items: state.items.filter((item)=> { return !(item.id === action.itemId); }) } break; } } } const ListReducer = (state = {items: []}, action) => { const { type } = action; switch (type) { case "GET_ITEM_LIST": return { ...state, items: action.items } break; case "CLEAR_ITEM_LIST": return { ...state, items: [] } break; } } } const reducer = combineReducers({itemReducer,ListReducer}) |
可以看到,Redux其實本身也提供了combineReducers方法來幫助開發者合併Reducer,可以讓每個Reducer互相獨立。
Middleware中介軟體
Redux中,一切資料都是從一個狀態到另一個狀態,那麼也許我們需要在這個狀態間新增一些自己的方法或者功能呢?
這個時候就需要Middleware,Middleware發生在傳送Action時,也就是呼叫store.dispatch()。
在Middleware中,我們通過呼叫next(action)函式就可以把Action傳遞給Reducer。
例如一個典型的簡易版redux-logger模組,
import { createStore, applyMiddleware } from 'redux'; const initialState = { items: [] }; const reducer = (state = initialState, action = {}) => { const { type } = action; switch (type) { case "ADD_ITEM": return { ...state, items: state.items.concat(action.item) } break; case "GET_ITEM_LIST": return { ...state, items: action.items } break; } } const reduxlog = store => next => action => { console.log(`prev state`, store.getState()) console.log(`action:`, action.type) next(action) console.log(`next state`,store.getState()) } const store = createStore(reducer, initialState, applyMiddleware(reduxlog)) store.subscribe(() => { console.log(store.getState()) }) store.dispatch({type: 'GET_ITEM_LIST','items':[{id:1,name:'test'}]}) store.dispatch({type: 'ADD_ITEM','item':[{id:2,name:'test2'}]}) |
中介軟體處理了改變前和改變後的狀態,寫法也非常容易理解,如果有業務需要對狀態集中處理,通過中介軟體的方式也不失為一種選擇。
文章頭部的那張圖,如果加上中介軟體,就是這樣:
中介軟體的順序
const store = createStore( reducer, applyMiddleware(thunk, promise, logger) ); |
中介軟體的呼叫順序其實還是有一定講究,這就要從Middleware本身的設計思想來說明。
Redux深受函數語言程式設計的影響,中介軟體的設計也不例外,
middleware 的設計有點特殊,是一個層層包裹的匿名函式,這其實是函數語言程式設計中的柯里化 curry,一種使用匿名單引數函式來實現多引數函式的方法。applyMiddleware 會對 logger 這個 middleware 進行層層呼叫,動態地對 store 和 next 引數賦值。
Redux的applyMiddleware會將所有的中介軟體組合串聯,
compose 將 chain 中的所有匿名函式,[f1, f2, … , fx, …, fn],組裝成一個新的函式,即新的 dispatch,當新 dispatch 執行時,[f1, f2, … , fx, …, fn],從左到右依次執行( 所以順序很重要)
具體中介軟體的實現思路不詳細展開,知乎上有一篇專欄分析的很到位,有興趣可以看一下 連結
上面的例子裡如果logger中介軟體不放置在最後面,輸出結果會不正確。
非同步問題
剛剛我們看了那麼多示例程式碼?但是至此,Redux一直沒有解決非同步的問題。試想,如果我在頁面輸入一段內容,然後觸發了一個動作,此時需要向服務端請求資料並將返回的資料展示出來。這是一個很常見的需求,但是涉及到非同步請求,剛剛的示例中的方法已經不再適用了。那麼Redux是如何解決非同步問題的呢?
沒錯,還是Middleware,Middleware只關注dispatch函式的傳遞,至於在傳遞的過程中幹了什麼中介軟體並不關心。
這裡不得不提redux-thunk這個中介軟體
redux-thunk的基本思想就是通過函式來封裝非同步請求,也就是說在actionCreater中返回一個函式,在這個函式中進行非同步呼叫。
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; |
其實開啟redux-thunk的原始碼看一下,講真程式碼量就十幾行,那麼這裡做了什麼處理呢?
其實關鍵的就一句程式碼
if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } |
我們知道Action正常其實返回的是一個js物件,如果沒有經過Middleware的處理,是不符合Redux邏輯,會丟擲異常的,所以redux-thunk只做了一件事件,那就是讓Action能夠相容 return 函式,redux-thunk內部再去消化掉這個函式。
function actionCreate() { return function (dispatch, getState) { api.fetch(data).then(function (json) { dispatch(json); }) } } |
總結
以上是最近一段時間通過在業務不斷實踐,然後學習和思考的總結。
在這期間發現,很多東西需要事先去積累,要拓寬自己的視野,如果自己不知道某種設計思路並且沒有相關經驗時,很難想到點上。
技術與業務是相輔相成的,技術能夠很好的幫助自己拓寬視野,能設計更好的專案架構;而業務能夠讓技術得以實踐,並且發現技術上可能還存在的問題,從而積累經驗。
這個思路我通過這段時間的學習感悟到的,今後的學習也會沿著這個思路走下去。
© 2016 - 2017 alisec-ued, powered by Hexo