【全棧React】第21天: Redux中間件

本文轉載自:衆成翻譯
譯者: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中間件

中間件一般指的是軟件服務, "粘合在一塊兒" 在現有軟件中的獨立功能。對於Redux, 中間件提供了一個 第三方擴展點, 在分發動做和將分發交給歸併器之間:api

[ Action ] [ Middleware ] [ Dispatcher ]promise

[ 動做 ] [ 中間件 ] [ 分發 ]瀏覽器

中間件的示例包括日誌記錄、崩潰報告、路由、處理異步請求等。緩存

讓咱們來處理異步請求, 就像對服務器的 HTTP 調用那樣。中間件是一個很好的地方。服務器

咱們中間件api

咱們將實現一些中間件, 它將表明咱們處理異步請求。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 中間件須要採起的步驟:

  1. 從 meta 中查找請求 URL 並撰寫請求選項

  2. 提出要求

  3. 將請求轉換爲 JavaScript 對象

  4. 回覆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周!

圖片描述

相關文章
相關標籤/搜索