【全栈React】第21天: Redux中间件

前端之家收集整理的这篇文章主要介绍了【全栈React】第21天: Redux中间件前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本文转载自:众成翻译
译者:iOSDevLog
链接http://www.zcfy.cc/article/3810
原文:https://www.fullstackreact.com/30-days-of-react/day-21/

今天,我们在Redux方法中使用Redux中间件来管理我们的代码中的复杂状态变化。

昨天,我们连接的点与Redux,从工作通过归并器,更新行动的创造者,并连接Redux到React组件。 Redux中间件 将解锁更多的权力,我们今天将会触及。

Redux中间件

中间件通常指的是软件服务,"粘合在一起" 在现有软件中的独立功能。对于Redux,中间件提供了一个 第三方扩展点,在分发动作和将分发交给归并器之间:

[ Action ] [ Middleware ] [ Dispatcher ]

[ 动作 ] [ 中间件 ] [ 分发 ]

中间件的示例包括日志记录、崩溃报告、路由、处理异步请求等。

让我们来处理异步请求,就像对服务器的 HTTP 调用那样。中间件是一个很好的地方。

我们中间件api

我们将实现一些中间件,它将代表我们处理异步请求。

中间件位于动作和归并器之间。它可以监听所有的调度和执行代码与行动和当前状态的细节。中间件提供了一个强大的抽象。让我们来看看如何使用它来管理我们自己的。

继续我们从昨天开始的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,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({},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({},{
        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,)
  );

  return store;
}

export default configureStore;

请注意,我们不必更改视图的 _任意_代码 以更新数据在状态树中的填充方式。很漂亮吧?

这个中间件非常简单,但它是构建它的良好基础。您是否可以考虑如何实现缓存服务,以便我们不需要对已有的数据进行请求?如何让一个跟踪挂起的请求,这样我们就可以为未完成的请求显示一个微调框?

太棒了!现在我们真的是Redux忍者。我们已经征服了Redux大山,并准备继续下一步的行动。在我们去之前,但是..。我们已经完成了3周!

猜你在找的React相关文章