本文转载自:众成翻译
译者: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,
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 中间件需要采取的步骤:
从 meta 中查找请求 URL 并撰写请求选项
提出要求
将请求转换为 JavaScript 对象
回复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周!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。