本文转载自:众成翻译
译者: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 中间件需要采取的步骤:
让我们采取这按步就班的步骤。首先,关闭 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周!