本文轉載自:衆成翻譯
譯者:iOSDevLog
連接:http://www.zcfy.cc/article/3810
原文:https://www.fullstackreact.com/30-days-of-react/day-21/react
今天,咱們在Redux方法中使用Redux中間件來管理咱們的代碼中的複雜狀態變化。json
昨天, 咱們鏈接的點與Redux, 從工做經過歸併器, 更新行動的創造者, 並鏈接Redux到React組件。 Redux中間件 將解鎖更多的權力, 咱們今天將會觸及。redux
中間件一般指的是軟件服務, "粘合在一塊兒" 在現有軟件中的獨立功能。對於Redux, 中間件提供了一個 第三方擴展點, 在分發動做和將分發交給歸併器之間:api
[ Action ] [ Middleware ] [ Dispatcher ]promise
[ 動做 ] [ 中間件 ] [ 分發 ]瀏覽器
中間件的示例包括日誌記錄、崩潰報告、路由、處理異步請求等。緩存
讓咱們來處理異步請求, 就像對服務器的 HTTP 調用那樣。中間件是一個很好的地方。服務器
咱們將實現一些中間件, 它將表明咱們處理異步請求。app
中間件位於動做和歸併器之間。它能夠監聽全部的調度和執行代碼與行動和當前狀態的細節。中間件提供了一個強大的抽象。讓咱們來看看如何使用它來管理咱們本身的。異步
繼續咱們從昨天開始的currentTime
Redux的工做, 讓咱們構建咱們的中間件, 以獲取當前的時間從服務器, 咱們用幾天前寫的真實從 API 服務獲取時間。
在咱們作得太多以前, 讓咱們從reducers.js
文件的rootReducer
中取出currentTime
的放到它本身的文件。咱們離開了根歸併器在一個狀態, 咱們保持 currentTime
工做在根歸併器。一般來講, 咱們將這些文件移動到他們本身的文檔中, 並使用rootReducer.js
文件 (咱們稱之爲reducers.js
) 來保持主組合歸併器。
First, let's pull the work into it's own file in redux/currentTime.js
. We'll export two objects from here (and each reducer):首先, 讓咱們把工做歸入到它本身的redux/currentTime.js
文件。咱們將從這裏 (和每一個歸併器) 導出兩個對象:
initialState
- 狀態樹的這個分支的初始狀態
reducer
-這個分支的歸併器
import * as types from './types'; export const initialState = { currentTime: new Date().toString(), } export const reducer = (state = initialState, action) => { switch(action.type) { case types.FETCH_NEW_TIME: return { ...state, currentTime: action.payload} default: return state; } } export default reducer
根歸併器用咱們的currentTime
, 咱們將須要更新reducers.js
文件接受新文件到根歸併器。幸運的是, 這很簡單:
import { combineReducers } from 'redux'; import * as currentUser from './currentUser'; import * as currentTime from './currentTime'; export const rootReducer = combineReducers({ currentTime: currentTime.reducer, currentUser: currentUser.reducer, }) export const initialState = { currentTime: currentTime.initialState, currentUser: currentUser.initialState, } export default rootReducer
最後, 讓咱們更新configureStore
函數, 從文件中提取 rootReducer 和初始狀態:
import { rootReducer, initialState } from './reducers' // ... export const configureStore = () => { const store = createStore( rootReducer, initialState, ); return store; }
中間件基本上是一個接受store
函數, 它將返回一個接受next
函數, 這將返回一個接受動做的函數。有點亂?讓咱們看看這意味着什麼。
讓咱們構建最小的中間件, 咱們可能可以準確地理解到底發生了什麼, 以及如何將它添加到咱們的棧中。
讓咱們建立咱們的第一個中間件。
如今, 中間件的簽名看起來像這樣:
const loggingMiddleware = (store) => (next) => (action) => { // Our middleware }
對這個中間件的事情很迷惑?別擔憂, 咱們都是第一次看到它。讓咱們把它剝離回來一點點, 拆解發生了什麼事。上面的loggingMiddleware
描述能夠像下面這樣重寫:
const loggingMiddleware = function(store) { // Called when calling applyMiddleware so // our middleware can have access to the store return function(next) { // next is the following action to be run // after this middleware return function(action) { // finally, this is where our logic lives for // our middleware. } } }
咱們不須要擔憂 怎麼 被調用, 只是它確實獲得了這個順序調用。讓咱們加強咱們的loggingMiddleware
, 這樣咱們實際上就能夠註銷被調用的動做:
const loggingMiddleware = (store) => (next) => (action) => { // Our middleware console.log(`Redux Log:`, action) // call the next function next(action); }
Our middleware causes our store to, when every time an action is called, we'll get a console.log
with the details of the action.咱們的中間件致使咱們的存儲被調用,咱們會獲得一個console.log
動做細節。
爲了將中間件應用到咱們的棧中, 咱們將用這個恰當命名的applyMiddleware
函數做爲 createStore()
方法的第三個參數。
import { createStore, applyMiddleware } from 'redux';
對於 應用 中間件, 咱們能夠在 createStore()
方法中調用這個 applyMiddleware()
函數。在咱們的 src/redux/configureStore.js
文件中, 讓咱們經過添加對applyMiddleware()
的調用來更新存儲建立:
const store = createStore( rootReducer, initialState, applyMiddleware( apiMiddleware, loggingMiddleware, ) );
如今咱們的中間件已經到位。在瀏覽器中打開控制檯以查看此演示所調用的全部動做。嘗試單擊打開控制檯的Update
按鈕.。
正如咱們所看到的, 中間件使咱們可以在咱們的Redux動做調用鏈中插入一個函數。在該函數中, 咱們能夠訪問該動做、狀態, 並且咱們還可以分發其餘動做。
咱們但願編寫一個能夠處理 API 請求的中間件函數。咱們能夠編寫一箇中間件函數, 它只偵聽與 API 請求對應的動做。咱們的中間件能夠 "監視" 具備特殊標記的動做。例如, 咱們能夠有一個 meta
對象的行動與 type
的 'api'
。咱們可使用它來確保咱們的中間件不處理與 API 請求無關的任何動做:
const apiMiddleware = store => next => action => { if (!action.meta || action.meta.type !== 'api') { return next(action); } // This is an api request }
若是某個動做有一個帶有 'api'
,類型的元對象, 咱們將在 apiMiddleware
.中接收該請求。
讓咱們轉換咱們的updateTime()
actionCreator, 將這些屬性包含到一個 API 請求中。讓咱們打開咱們一直在使用的currentTime
Redux模塊 (在src/redux/currentTime.js
), 並找到fetchNewTime()
函數定義。
讓咱們把這個請求的 URL 傳遞給咱們的meta
對象。咱們甚至能夠從調用動做建立者的內部接受參數:
const host = 'https://andthetimeis.com' export const fetchNewTime = ({ timezone = 'pst', str='now'}) => ({ type: types.FETCH_NEW_TIME, payload: new Date().toString(), meta: { type: 'api', url: host + '/' + timezone + '/' + str + '.json' } })
當咱們按下按鈕更新的時間, 咱們的apiMiddleware
將結束了在歸併器以前截取。對於咱們在中間件中捕獲的任何調用, 咱們能夠將元對象拆分, 並使用這些選項進行請求。或者, 咱們能夠經過fetch()
API 將整個被消毒的meta
對象傳遞出去。
咱們的 API 中間件須要採起的步驟:
從 meta 中查找請求 URL 並撰寫請求選項
提出要求
將請求轉換爲 JavaScript 對象
回覆Redux/用戶
讓咱們採起這按步就班的步驟。首先, 關閉 URL
並建立fetchOptions
以傳遞到fetch()
。咱們將在下面的代碼中的註釋中列出這些步驟:
const apiMiddleware = store => next => action => { if (!action.meta || action.meta.type !== 'api') { return next(action); } // This is an api request // Find the request URL and compose request options from meta const {url} = action.meta; const fetchOptions = Object.assign({}, action.meta); // Make the request fetch(url, fetchOptions) // convert the response to json .then(resp => resp.json()) .then(json => { // respond back to the user // by dispatching the original action without // the meta object let newAction = Object.assign({}, action, { payload: json.dateString }); delete newAction.meta; store.dispatch(newAction); }) } export default apiMiddleware
咱們有幾個選項, 咱們如何回覆到Redux鏈中的用戶。就我的而言, 咱們更喜歡用相同的類型響應請求被激發, 而沒有 meta
標記, 並將響應體做爲新動做的 payload 有效負載
。
這樣, 咱們不須要改變咱們的Redux歸併器來管理響應任何不一樣的, 若是咱們沒有提出要求。
咱們也不限於一個單一的響應。假設咱們的用戶在請求完成時經過了onSuccess
回調來調用。咱們能夠調用這個onSuccess
回調, 而後發送備份鏈:
const apiMiddleware = store => next => action => { if (!action.meta || action.meta.type !== 'api') { return next(action); } // This is an api request // Find the request URL and compose request options from meta const {url} = action.meta; const fetchOptions = Object.assign({}, action.meta); // Make the request fetch(url, fetchOptions) // convert the response to json .then(resp => resp.json()) .then(json => { if (typeof action.meta.onSuccess === 'function') { action.meta.onSuccess(json); } return json; // For the next promise in the chain }) .then(json => { // respond back to the user // by dispatching the original action without // the meta object let newAction = Object.assign({}, action, { payload: json.dateString }); delete newAction.meta; store.dispatch(newAction); }) }
這裏的可能性幾乎是無止境的。讓咱們添加apiMiddleware
到咱們的鏈經過它更新configureStore()
函數:
import { createStore, applyMiddleware } from 'redux'; import { rootReducer, initialState } from './reducers' import loggingMiddleware from './loggingMiddleware'; import apiMiddleware from './apiMiddleware'; export const configureStore = () => { const store = createStore( rootReducer, initialState, applyMiddleware( apiMiddleware, loggingMiddleware, ) ); return store; } export default configureStore;
請注意, 咱們沒必要更改視圖的 _任意_代碼 以更新數據在狀態樹中的填充方式。很漂亮吧?
這個中間件很是簡單, 但它是構建它的良好基礎。您是否能夠考慮如何實現緩存服務, 以便咱們不須要對已有的數據進行請求?如何讓一個跟蹤掛起的請求, 這樣咱們就能夠爲未完成的請求顯示一個微調框?
太棒了!如今咱們真的是Redux忍者。咱們已經征服了Redux大山, 並準備繼續下一步的行動。在咱們去以前, 可是..。咱們已經完成了3周!