reducer是redux的三個核心概念之一,它指定了應用狀態的變化如何響應 actions 併傳送到 store,需要由開發人員自己定義,提起reducer,最常想到的一個準則是reducer要是純函式,那麼這其中是什麼原因呢,如果不是純函式的話會導致redux不可用嗎?接下來就來一步步分析下這個問題
reducer響應actions的原理
我們定義的reducer負責接收action,並返回一個新的state,但在react元件開發中,我們也僅僅定義了而從未實際呼叫過reducer,所有reducer作用的原理是什麼呢?
實際上,元件中我們會手動呼叫dispatch方法傳送action來響應事件,而這裡的傳送action不止傳送action那麼簡單,還包括了呼叫reducer,更新狀態為reducer的處理結果,觸發訂閱事件等一系列操作,都是在dispatch的方法內實現的,到這裡可以來通過原始碼的處理流程來了解一些這個過程~
function dispatch(action) {
...
try {
//將flag置為true,表明處於分發邏輯中
isDispatching = true
//currentReducer即為傳入的reducer函式,這裡會自動呼叫currentReducer函式,並將返回值賦給currentState
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
//呼叫訂閱函式
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
複製程式碼
可以看到,dispatch處理中reducer方法的呼叫無疑是很關鍵的一步,經過它的處理之後才生成了新的狀態資料 在redux中,保持reducer是一個純函式非常重要,保證reducer是純函式要達到以下幾點:
- 不得修改傳入的引數
- 不得呼叫非純函式,如Date.now()
- 不得執行有副作用的操作,如API請求和路由跳轉
那麼為什麼reducer函式要遵守這幾點規則呢,如果不遵守的話又會怎麼樣呢
純函式條件之一:不得修改傳入的引數
function reuderfunc(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return ...
default:
return state
}
}
複製程式碼
reducer接受的引數由兩個,一個是action
,一個是state
,其中action
只是用來傳遞資訊的,完全沒有修改的必要,那麼state
呢,既然reducer
是用來返回一個新的state
的,在這個過程中直接基於傳入的state
來改,然後再返回,貌似也可以達到效果?
實際上,雖然在reducer處理過程中改變了傳入的state,有可能redux還會正常運轉,但像時間旅行,錄製和回放這類依賴於歷史狀態的功能則無法實現了,要知道這可是redux當時的設計初衷之一~
特別注意的是,如果改變了傳入的引數initState,或該物件底層的任意一個key值,都有可能在應用於react元件時,導致react任為該狀態無變化,而不更新元件,因為redux原始碼中將oldState和newState(reducer返回的結果)做比較,如果某一級state指定的引用相等,則會導致此結果,這樣做是犧牲一點計算效能(生成新物件)來保證頁面重新整理。
純函式條件之二:不得呼叫非純函式,如 Date.now() 或 Math.random()
redux的核心提供可預測化的狀態管理,即無論何時特定的action觸發的行為永遠保持一致,試想如果reducer中有Date.now()等非純函式,即使同樣的action,那麼reducer處理過程中也是有所不同的,不再能保證可預測性
純函式條件之二:執行有副作用的操作
首先,執行有副作用的操作,如api和路由跳轉,因為設定到後臺的處理,會帶來和上一節同樣的問題,同樣的action觸發後的處理過程可能有所不同(依據後臺處理或路由跳轉而定),失去了可預測性 其次因為reducer函式的返回值是要作為下一個狀態值被返回的,那麼試想當reducer中有api呼叫時,api是會向後臺請求資料的非同步函式,往往希望後臺的請求結果資料會應用於新的state,但是這時候會發現,這個非同步函式解除安裝reducer中沒有辦法影響到state的更新,因為在非同步請求處理完成時,reducer函式已經被返回(函式的返回是同步的),所以說在redcuer中呼叫api也完全沒有意義了.
api請求該如何執行
剛才提到在reducer中不能執行api請求操作,但是很明顯api操作是不可避免的,因為總要向後臺請求資料,那麼api請求應該如何做呢?這裡有兩個辦法
- 在dispatch方法之前進行api請求:在dispatch之外先進行api非同步請求,當收到請求結果後,根據結果的不同選擇dispatch不同的action
- 應用redux-thunk,redux-promise等中介軟體,就可以在dispatch函式中直接執行api請求等非同步操作了。