1. 程式人生 > >圖解Redux資料流(二)

圖解Redux資料流(二)

圖解Redux資料流(二)

Nov 28, 2016

上一篇文章回答了前兩個問題,而這一篇則回答最後一個問題。

Redux如何使用?

前一篇文章描述了Redux的基本要素,並且梳理了一下Redux的資料流動圖

Alt text

首先,我們的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'}]})

Alt text

中介軟體處理了改變前和改變後的狀態,寫法也非常容易理解,如果有業務需要對狀態集中處理,通過中介軟體的方式也不失為一種選擇。
文章頭部的那張圖,如果加上中介軟體,就是這樣:

Alt text

中介軟體的順序

 

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);

})

}

}

總結

以上是最近一段時間通過在業務不斷實踐,然後學習和思考的總結。

在這期間發現,很多東西需要事先去積累,要拓寬自己的視野,如果自己不知道某種設計思路並且沒有相關經驗時,很難想到點上。

技術與業務是相輔相成的,技術能夠很好的幫助自己拓寬視野,能設計更好的專案架構;而業務能夠讓技術得以實踐,並且發現技術上可能還存在的問題,從而積累經驗。

這個思路我通過這段時間的學習感悟到的,今後的學習也會沿著這個思路走下去。

PREVNEXT

© 2016 - 2017 alisec-ued, powered by Hexo