30天入坑React ---------------day21 Redux MiddleWare

这篇文章是30天React系列的一部分 。

在本系列中,我们将从非常基础开始,逐步了解您需要了解的所有内容,以便开始使用React。如果您曾经想学习React,那么这里就是您的最佳选择!

下载免费的PDF

30天的React Mini-Ebook

 

Redux Middleware(Redux中间件)

在Github上编辑此页面


我的实例

https://codesandbox.io/s/wwyjpm82j8  


Today, we're looking at the Redux method of managing complex state changes in our code using Redux middleware.

今天,我们正在研究使用Redux中间件管理代码中复杂状态更改的Redux方法。

Yesterday we connected the dots with Redux, from working through reducers, updating action creators, and connecting Redux to React components. Redux middleware unlocks even more power which we'll touch on today.

昨天我们将点与Redux联系起来,从使用reducers,更新动作创建器以及将Redux连接到React组件。Redux中间件解锁了我们今天要触及的更多功能。

Redux middleware(Redux中间件)

Middleware generally refers to software services that "glue together" separate features in existing software. For Redux, middleware provides a third-party extension point between dispatching an action and handing the action off to the reducer:

[ Action ] <-> [ Middleware ] <-> [ Dispatcher ]

中间件通常是指将现有软件中的单独功能“粘合在一起”的软件服务。对于Redux,中间件在分派操作和将操作交给reducer之间提供第三方扩展点:

[行动] < - > [中间件] < - > [调度员]

Examples of middleware include logging, crash reporting, routing, handling asynchronous requests, etc.

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

Let's take the case of handling asynchronous requests, like an HTTP call to a server. Middleware is a great spot to do this.

我们来看一下处理异步请求的情况,比如对服务器的HTTP调用。中间件是一个很好的选择。

Our API middleware (我们的API中间件)

We'll implement some middleware that will handle making asynchronous requests on our behalf.

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

Middleware sits between the action and the reducer. It can listen for all dispatches and execute code with the details of the actions and the current states. Middleware provides a powerful abstraction. Let's see exactly how we can use it to manage our own.

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

Continuing with our currentTime redux work from yesterday, let's build our middleware to fetch the current time from the server we used a few days ago to actually GET the time from the API service.

currentTime从昨天开始继续我们的redux工作,让我们构建我们的中间件来从我们几天前使用的服务器中获取当前时间,以实际从API服务中获取时间。

Before we get too much further, let's pull out the currentTime work from the rootReducer in the reducers.js file out to it's own file. We left the root reducer in a state where we kept the currentTime work in the root reducer. More conventionally, we'll move these in their own files and use the rootReducer.js file (which we called reducers.js) to hold just the main combination reducer.

在我们进一步深入之前,让我们将文件中的currentTime工作从它自己的文件中拉出来。我们将根减速器留在了我们将工作保留在减速器中的状态。朗读更常规的是,我们将在他们自己的文件中移动它们并使用rootReducer.js文件(我们称之为reducers.js)来保存主组合reducer。

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):

  • initialState - the initial state for this branch of the state tree
  • reducer - this branch's reducer

 

首先,让我们把工作放入它自己的文件中redux/currentTime.js。我们将从这里(和每个reducer)导出两个对象:

  • 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

With our currentTime out of the root reducer, we'll need to update the reducers.js file to accept the new file into the root reducer. Luckily, this is pretty easy: 

使用我们currentTime的root减速器,我们需要更新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

Lastly, let's update the configureStore function to pull the rootReducer and initial state from the file: 

最后,让我们更新configureStore函数以从文件中提取rootReducer和初始状态:

import { rootReducer, initialState } from './reducers'
// ...
export const configureStore = () => {
  const store = createStore(
    rootReducer,
    initialState,
  );

  return store;
}

Back to middleware(回到中间件)

Middleware is basically a function that accepts the store, which is expected to return a function that accepts the next function, which is expected to return a function which accepts an action. Confusing? Let's look at what this means.

中间件基本上store是一个接受的next函数,它可以返回一个接受函数的函数,该函数需要返回一个接受一个动作的函数。混乱?让我们来看看这意味着什么。

 

The simplest middleware possible  (最简单的中间件可能)

Let's build the smallest middleware we possibly can to understand exactly what's happening and how to add it to our stack.

Let's create our first middleware.

Now the signature of middleware looks like this:

 

让我们构建最小的中间件,我们可以准确理解发生了什么以及如何将它添加到我们的堆栈中。

让我们创建我们的第一个中间件。

现在中间件的签名看起来像这样:

const loggingMiddleware = (store) => (next) => (action) => {
  // Our middleware
}

Befuddled about this middleware thing? Don't worry, we all are the first time we see it. Let's peel it back a little bit and destructure what's going on. That loggingMiddleware description above could be rewritten like the following: 

对这个中间件的事情感到困惑?别担心,我们都是第一次看到它。让我们把它剥掉一点,然后解构正在发生的事情。这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.
    }
  }
}

We don't need to worry about how this gets called, just that it does get called in that order. Let's enhance our loggingMiddleware so that we do actually log out the action that gets called: 

我们不需要担心如何调用它,只需要按顺序调用它。让我们增强我们,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详细的动作。

In order to apply middleware to our stack, we'll use this aptly named applyMiddleware function as the third argument to the createStore() method.

为了将中间件应用到我们的堆栈,我们将使用这个恰当命名的applyMiddleware函数作为该createStore()方法的第三个参数。

import { createStore, applyMiddleware } from 'redux';

To apply middleware, we can call this applyMiddleware() function in the createStore() method. In our src/redux/configureStore.js file, let's update the store creation by adding a call to applyMiddleware():

应用中间件,我们可以applyMiddleware()createStore()方法中调用此函数。在我们的src/redux/configureStore.js文件中,让我们通过添加对以下内容的调用来更新商店创建applyMiddleware()

  const store = createStore(
    rootReducer,
    initialState,
    applyMiddleware(
      apiMiddleware,
      loggingMiddleware,
    )
  );

 

Now our middleware is in place. Open up the console in your browser to see all the actions that are being called for this demo. Try clicking on the Update button with the console open...

现在我们的中间件到位了。在浏览器中打开控制台,查看为此演示调用的所有操作。尝试Update在控制台打开时单击按钮...

As we've seen, middleware gives us the ability to insert a function in our Redux action call chain. Inside that function, we have access to the action, state, and we can dispatch other actions.

正如我们所见,中间件使我们能够在Redux动作调用链中插入一个函数。在该功能内部,我们可以访问操作,状态,并且我们可以调度其他操作。

We want to write a middleware function that can handle API requests. We can write a middleware function that listens only to actions corresponding to API requests. Our middleware can "watch" for actions that have a special marker. For instance, we can have a meta object on the action with a type of 'api'. We can use this to ensure our middleware does not handle any actions that are not related to API requests:

我们想编写一个可以处理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
}

If an action does have a meta object with a type of 'api', we'll pick up the request in the apiMiddleware

如果一个动作确实有一个类型为的meta对象'api',我们将在中获取请求apiMiddleware

Let's convert our updateTime() actionCreator to include these properties into an API request. Let's open up the currentTime redux module we've been working with (in src/redux/currentTime.js) and find the fetchNewTime() function definition.

让我们将updateTime()actionCreator 转换为将这些属性包含在API请求中。让我们打开currentTime我们一直在使用的redux模块(in src/redux/currentTime.js)并找到fetchNewTime()函数定义。

Let's pass in the URL to our meta object for this request. We can even accept parameters from inside the call to the action creator:

让我们将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'
  }
})

When we press the button to update the time, our apiMiddleware will catch this before it ends up in the reducer. For any calls that we catch in the middleware, we can pick apart the meta object and make requests using these options. Alternatively, we can just pass the entire sanitized meta object through the fetch() API as-is.

当我们按下按钮更新时间时,我们apiMiddleware会在它最终进入减速器之前捕获它。对于我们在中间件中捕获的任何调用,我们可以分离元对象并使用这些选项发出请求。或者,我们可以按原样meta通过fetch()API 传递整个已清理的对象。

 

The steps our API middleware will have to take:

  1. Find the request URL and compose request options from meta
  2. Make the request
  3. Convert the request to a JavaScript object
  4. Respond back to Redux/user

我们的API中间件必须采取的步骤:

  1. 从meta查找请求URL并撰写请求选项
  2. 提出要求
  3. 将请求转换为JavaScript对象
  4. 回复Redux /用户

Let's take this step-by-step. First, to pull off the URL and create the fetchOptionsto pass to fetch(). We'll put these steps in the comments in the code below:

让我们一步一步来。首先,拉开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

We have several options for how we respond back to the user in the Redux chain. Personally, we prefer to respond with the same type the request was fired off without the meta tag and placing the response body as the payload of the new action.

我们有多种选择可以回复Redux链中的用户。就个人而言,我们更倾向于使用相同类型的响应,在没有meta标记的情况下触发请求,并将响应主体放置为payload新操作的响应主体。

In this way, we don't have to change our redux reducer to manage the response any differently than if we weren't making a request.

通过这种方式,我们不必更改我们的redux减速器来管理响应,就像我们没有提出请求一样。

We're also not limited to a single response either. Let's say that our user passed in an onSuccess callback to be called when the request was complete. We could call that onSuccess callback and then dispatch back up the chain:

我们也不仅限于单一回应。假设我们的用户在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);
    })
}

The possibilities here are virtually endless. Let's add the apiMiddleware to our chain by updating it in the configureStore() function:

这里的可能性几乎无穷无尽。让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;

Notice that we didn't have to change any of our view code to update how the data was populated in the state tree. Pretty nifty, eh?

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

This middleware is pretty simplistic, but it's a good solid basis for building it out. Can you think of how you might implement a caching service, so that we don't need to make a request for data we already have? How about one to keep track of pending requests, so we can show a spinner for requests that are outstanding?

这个中间件非常简单,但它是构建它的良好基础。您能想到如何实现缓存服务,以便我们不需要对已有的数据进行请求吗?如何跟踪待处理的请求,以便我们可以为未完成的请求显示微调器?

Awesome! Now we really are Redux ninjas. We've conquered the Redux mountain and are ready to move on to the next step. Before we head there, however... pat yourself on the back. We've made it through week 3!

真棒!现在我们真的是Redux忍者。我们已经征服了Redux山,准备继续下一步。然而,在我们前往那里之前......拍拍你自己。我们已经完成了第3周!

学习REACT正确的方法

React和朋友的最新,深入,完整的指南。

下载第一章

❮上一个

下一章:

测试简介

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值