【全栈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周!

图片描述

相关文章
相关标签/搜索