參考:https://blog.csdn.net/hsany330/article/details/105951197
前面我們有說過,redux由三大核心組成部分:reducer、action、store。
其中,Reducer是一個純函數,只承擔計算 State 的功能,不合適承擔其他功能,也承擔不了,因爲理論上,純函數不能進行讀寫操作。
所以就有了redux中間件。
默認情況下,Redux 自身只能處理同步數據流。但是在實際項目開發中,狀態的更新、獲取,通常是使用異步操作來實現。
- 問題:如何在 Redux 中進行異步操作呢?
- 回答:通過 Redux 中間件機制來實現
1.redux中間件概念
- 中間件,可以理解爲處理一個功能的中間環節
- 中間件的優勢:可以串聯、組合,在一個項目中使用多箇中間件
- Redux 中間件用來處理 狀態 更新,也就是在 狀態 更新的過程中,執行一系列的相應操作
dispatch一個action之後,到達reducer之前,進行一些額外的操作,就需要用到middleware。你可以利用 Redux middleware 來進行日誌記錄、創建崩潰報告、調用異步接口或者路由等等。
換言之,中間件都是對store.dispatch()的增強
2.中間件的觸發時機
- Redux 中間件執行時機:在 dispatching action 和 到達 reducer 之間。
- 沒有中間件:
dispatch(action) => reducer
- 使用中間件:
dispatch(action) => 執行中間件代碼 => reducer
原理:封裝了 redux 自己的 dispatch 方法
沒有中間件:store.dispatch() 就是 Redux 庫自己提供的 dispatch 方法,用來發起狀態更新
使用中間件:store.dispatch() 就是中間件封裝處理後的 dispatch,但是,最終一定會調用 Redux 自己的 dispatch 方法發起狀態更新
redux的異步數據流動過程:
3.中間件的原理
Redux 中間件原理:創建一個函數,包裝 store.dispatch,使用新創建的函數作爲新的 dispatch
比如下圖,logger 就是一箇中間件,使用該中間件後 store.dispatch 就是包裝後的新 dispatch
中間件修改了 store.dispatch,在分發動作和到達 reducer 之間提供了擴展
redux 中間件採用了 洋蔥模型 來實現
-
自己實現記錄日誌的 redux 中間件:
// 簡化寫法: // store 表示:redux 的 store // next 表示:下一個中間件,如果只使用一箇中間,那麼 next 就是 store.dispatch(redux 自己的 dispatch 函數) // action 表示:要分發的動作 const logger = store => next => action => { console.log('prev state:', store.getState()) // 更新前的狀態 // 記錄日誌代碼 console.log('dispatching', action) // 如果只使用了一箇中間件: // 那麼,next 就表示原始的 dispatch // 也就是:logger中間件包裝了 store.dispatch let result = next(action) // 上面 next 代碼執行後,redux 狀態就已經更新了,所以,再 getState() 拿到的就是更新後的最新狀態值 // 記錄日誌代碼 console.log('next state', store.getState()) // 更新後的狀態 return result } // 完整寫法: const logger = store => { return next => { return action => { // 中間件代碼寫在這個位置: } } }
4.中間件的用法
redux中間件需要在createStore()生成store時註冊:
import { applyMiddleware, createStore, combineReducers } from 'redux'; import thunk from 'redux-thunk'; import reducer1 from '../reducer/reducer1'; import reducer2 from '../reducer/reducer2'; const reducers = combineReducers(reducer1); const store = createStore( reducers, applyMiddleware(thunk) ); export default store;
redux常見的中間件有redux-thunk,redux-logger,redux-saga等。
redux-devtools-extension中間件
目標:能夠使用chrome開發者工具調試跟蹤redux狀態
內容:
redux-devtools-exension 文檔:https://www.npmjs.com/package/redux-devtools-extension
先給 Chrome 瀏覽器安裝 redux 開發者工具,然後,就可以查看 Redux 狀態了
步驟:
1.安裝: yarn add redux-devtools-extension
2.從該中間件中導入 composeWithDevTools 函數
3.調用該函數,將 applyMiddleware() 作爲參數傳入
4.打開 Chrome 瀏覽器的 redux 開發者工具並使用
import thunk from 'redux-thunk' import { composeWithDevTools } from 'redux-devtools-extension' const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk))) export default store
redux-thunk中間件
源碼解讀,原理([email protected]源碼:https://github.com/reduxjs/redux-thunk/blob/v2.3.0/src/index.js):
function createThunkMiddleware(extraArgument) {
// Redux 中間件的寫法:const myMiddleware = store => next => action => { /* 此處寫 中間件 的代碼 */ }
return ({ dispatch, getState }) => (next) => (action) => {
// redux-thunk 的核心代碼:
// 判斷 action 的類型是不是函數
// 如果是函數,就調用該函數(action),並且傳入了 dispatch 和 getState
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 如果不是函數,就調用下一個中間件(next),將 action 傳遞過去
// 如果沒有其他中間件,那麼,此處的 next 指的就是:Redux 自己的 dispatch 方法
return next(action);
};
}
// 所以,在使用了 redux-thunk 中間件以後,那麼,redux 就既可以處理 對象形式的 action 又可以處理 函數形式的 action 了
// 1 處理對象形式的 action
dispatch({ type: 'todos/clearAll' }) // 對應上面第 14 行代碼
// 2 處理函數型的 action
export const clearAllAsync = () => {
return dispatch => {
// 在此處,執行異步操作
setTimeout(() => {
// 異步操作完成後,如果想要修改 redux 中的狀態,就必須要
// 分發一個 對象形式的 action(同步的 action)
dispatch({ type: types.CLEAR_ALL })
}, 1000)
}
}
dispatch(clearAllAsync()) // 對應上面第 8、9 行代碼
目標:能夠使用redux-thunk中間件處理異步操作
內容:
redux-thunk 中間件可以處理函數形式的 action。因此,在函數形式的 action 中就可以執行異步操作
語法:
thunk action 是一個函數
函數包含兩個參數:1 dispatch 2 getState
// 函數形式的 action const thunkAction = () => { return (dispatch, getState) => {} } // 解釋: const thunkAction = () => { // 注意:此處返回的是一個函數,返回的函數有兩個參數: // 第一個參數:dispatch 函數,用來分發 action // 第二個參數:getState 函數,用來獲取 redux 狀態 return (dispatch, getState) => { setTimeout(() => { // 執行異步操作 // 在異步操作成功後,可以繼續分發對象形式的 action 來更新狀態 }, 1000) } }
使用 redux-thunk 中間件前後對比:
1.不使用 redux-thunk 中間件,action 只能是一個對象
// 1 普通 action 對象 { type: 'counter/increment' } dispatch({ type: 'counter/increment' }) // 2 action creator const increment = payload => ({ type: 'counter/increment', payload }) dispatch(increment(2))
2.使用 redux-thunk 中間件後,action 既可以是對象,又可以是函數
// 1 對象: // 使用 action creator 返回對象 const increment = payload => ({ type: 'counter/increment', payload }) // 分發同步 action dispatch(increment(2)) // 2 函數: // 使用 action creator 返回函數 const incrementAsync = () => { return (dispatch, getState) => { // ... 執行異步操作代碼 } } // 分發異步 action dispatch(incrementAsync())
步驟:
- 安裝:
yarn add redux-thunk
- 導入 redux-thunk
- 將 thunk 添加到 applyMiddleware 函數的參數(中間件列表)中
- 創建函數形式的 action,在函數中執行異步操作
核心代碼:
store/index.js 中:
// 導入 thunk 中間件 import thunk from 'redux-thunk' // 將 thunk 添加到中間件列表中 // 知道:如果中間件中使用 logger 中間件,logger 中間件應該出現在 applyMiddleware 的最後一個參數 const store = createStore(rootReducer, applyMiddleware(thunk, logger))
actions/index.js 中:
export const clearAllAsync = () => { return (dispatch) => { // 處理異步的代碼:1 秒後再清理已完成任務 setTimeout(() => { dispatch(clearAll()) }, 1000) } }
App.js 中:
import { clearTodoAsync } from '../store/actions/todos' const TodoFooter = () => { return ( // ... <button className="clear-completed" onClick={() => dispatch(clearTodoAsync())} > Clear completed </button> ) }
redux-logger中間件
目標:能夠使用redux-logger中間件記錄日誌
步驟:
1.安裝:yarn add redux-logger
2.導入 redux-logger 中間件
3.從 redux 中導入 applyMiddleware 函數
4.調用 applyMiddleware() 並傳入 logger 中間件作爲參數
5.將 applyMiddleware() 調用作爲 createStore 函數的第二個參數
然後,調用 store.dispatch() 查看 console 中 logger 中間件記錄的日誌信息
核心代碼:
store/index.js 中:
import { createStore, applyMiddleware } from 'redux' import logger from 'redux-logger' import rootReducer from './reducers' const store = createStore(rootReducer, applyMiddleware(logger))
當然redux的中間件有很多,本文只講了其中的一部分。比如redux-saga也是解決redux中異步問題的中間件,redux-thunk是把異步操作放在action裏操作,而redux-saga的設計思想是把異步操作單獨拆分出來放在一個文件裏管理(面試常問)。
其他中間件,像redux-promise、redux-saga等,此處篇幅有限,改日再講。此處先略過。有興趣的可參考:https://blog.csdn.net/yrqlyq/article/details/119243072
好了,吹水到此。編程使我快樂,快樂讓我吹水。歡迎留言互動互粉!