配置您的商店
在 "Redux 基础知识" 教程 中,我们通过构建一个示例待办事项列表应用程序介绍了 Redux 的基本概念。作为其中的一部分,我们讨论了 如何创建和配置 Redux 商店。
现在我们将探索如何自定义商店以添加额外的功能。我们将从 "Redux 基础知识" 第 5 部分:UI 和 React 中的源代码开始。您可以在 Github 上的示例应用程序存储库 中查看此阶段教程的源代码,或者 通过 CodeSandbox 在您的浏览器中查看。
创建商店
首先,让我们看一下我们创建存储的原始 index.js
文件
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
import App from './components/App'
const store = createStore(rootReducer)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
在这段代码中,我们将我们的 reducer 传递给 Redux 的 createStore
函数,该函数返回一个 store
对象。然后,我们将此对象传递给 react-redux
的 Provider
组件,该组件渲染在我们组件树的顶部。
这确保了我们每次通过 react-redux
的 connect
连接到 Redux 时,存储都可供我们的组件使用。
扩展 Redux 功能
大多数应用程序通过添加中间件或存储增强器来扩展其 Redux 存储的功能(注意:中间件很常见,增强器不太常见)。中间件为 Redux 的 dispatch
函数添加了额外的功能;增强器为 Redux 存储添加了额外的功能。
我们将添加两个中间件和一个增强器
redux-thunk
中间件,它允许简单地异步使用 dispatch。- 一个记录已调度操作和结果的新状态的中间件。
- 一个增强器,它记录 reducer 处理每个操作所花费的时间。
安装 redux-thunk
npm install redux-thunk
middleware/logger.js
const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}
export default logger
enhancers/monitorReducer.js
const round = number => Math.round(number * 100) / 100
const monitorReducerEnhancer =
createStore => (reducer, initialState, enhancer) => {
const monitoredReducer = (state, action) => {
const start = performance.now()
const newState = reducer(state, action)
const end = performance.now()
const diff = round(end - start)
console.log('reducer process time:', diff)
return newState
}
return createStore(monitoredReducer, initialState, enhancer)
}
export default monitorReducerEnhancer
让我们将这些添加到我们现有的 index.js
中。
首先,我们需要导入
redux-thunk
以及我们的loggerMiddleware
和monitorReducerEnhancer
,以及 Redux 提供的另外两个函数:applyMiddleware
和compose
。然后,我们使用
applyMiddleware
创建一个存储增强器,它将我们的loggerMiddleware
和thunkMiddleware
应用于存储的 dispatch 函数。接下来,我们使用
compose
将我们新的middlewareEnhancer
和monitorReducerEnhancer
合并成一个函数。这是必需的,因为你只能将一个增强器传递给
createStore
。要使用多个增强器,你必须先将它们组合成一个更大的增强器,如本例所示。最后,我们将这个新的
composedEnhancers
函数作为第三个参数传递给createStore
。注意:第二个参数,我们将忽略它,允许你将预加载状态加载到存储中。
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { applyMiddleware, createStore, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from './reducers'
import loggerMiddleware from './middleware/logger'
import monitorReducerEnhancer from './enhancers/monitorReducer'
import App from './components/App'
const middlewareEnhancer = applyMiddleware(loggerMiddleware, thunkMiddleware)
const composedEnhancers = compose(middlewareEnhancer, monitorReducerEnhancer)
const store = createStore(rootReducer, undefined, composedEnhancers)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
这种方法的问题
虽然这段代码可以运行,但对于典型的应用程序来说,它并不理想。
大多数应用程序使用多个中间件,每个中间件通常都需要一些初始设置。添加到 index.js
中的额外噪音会很快使其难以维护,因为逻辑没有清晰地组织。
解决方案:configureStore
解决这个问题的方法是创建一个新的 configureStore
函数,它封装了我们的商店创建逻辑,然后可以将其放在自己的文件中以方便扩展。
最终目标是让我们的 index.js
看起来像这样
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import configureStore from './configureStore'
const store = configureStore()
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
与配置商店相关的所有逻辑(包括导入 reducer、中间件和增强器)都在一个专用文件中处理。
为了实现这一点,configureStore
函数看起来像这样
import { applyMiddleware, compose, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'
import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'
export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = compose(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
return store
}
此函数遵循上面概述的相同步骤,其中一些逻辑被拆分出来以准备扩展,这将使将来更容易添加更多内容
middlewares
和enhancers
都被定义为数组,与使用它们的函数分开。这使我们能够根据不同的条件轻松添加更多中间件或增强器。
例如,通常只在开发模式下添加一些中间件,这可以通过在 if 语句中将中间件推送到 middlewares 数组来轻松实现
if (process.env.NODE_ENV === 'development') {
middlewares.push(secretMiddleware)
}如果我们想稍后添加
preloadedState
变量,则将其传递给createStore
。
这也使我们的 createStore
函数更容易理解 - 每一步都清晰地分开,这使得更清楚地了解到底发生了什么。
集成开发者工具扩展
另一个您可能希望添加到应用程序中的常见功能是 redux-devtools-extension
集成。
该扩展是一套工具,可让您完全控制 Redux 商店 - 它允许您检查和重放操作、在不同时间探索您的状态、直接向商店分派操作等等。 单击此处阅读有关可用功能的更多信息。
有几种方法可以集成扩展,但我们将使用最方便的选择。
首先,我们通过 npm 安装包。
npm install --save-dev redux-devtools-extension
接下来,我们删除从 redux
导入的 compose
函数,并用从 redux-devtools-extension
导入的新 composeWithDevTools
函数替换它。
最终代码如下所示
import { applyMiddleware, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'
export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = composeWithDevTools(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
return store
}
就是这样!
如果我们现在通过安装了 devtools 扩展的浏览器访问我们的应用程序,我们可以使用强大的新工具进行探索和调试。
热重载
另一个可以使开发过程更加直观的强大工具是热重载,这意味着替换代码片段而无需重新启动整个应用程序。
例如,考虑当您运行应用程序,与它交互一段时间,然后决定更改其中一个 reducer 时会发生什么。通常,当您进行这些更改时,您的应用程序将重新启动,将 Redux 状态恢复到其初始值。
启用热模块重载后,只会重新加载您更改的 reducer,允许您更改代码而无需每次都重置状态。这使得开发过程更快。
我们将热重载添加到我们的 Redux reducer 和 React 组件中。
首先,让我们将其添加到我们的 configureStore
函数中
import { applyMiddleware, compose, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'
import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'
export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = compose(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
}
return store
}
新代码包装在 if
语句中,因此它只在我们的应用程序不在生产模式下并且 module.hot
功能可用时运行。
像 Webpack 和 Parcel 这样的捆绑器支持 module.hot.accept
方法来指定哪个模块应该热重载,以及模块更改时应该发生什么。在本例中,我们正在监视 ./reducers
模块,并在它更改时将更新的 rootReducer
传递给 store.replaceReducer
方法。
我们还将在我们的 index.js
中使用相同的模式来热重载对我们 React 组件的任何更改
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import configureStore from './configureStore'
const store = configureStore()
const renderApp = () =>
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./components/App', renderApp)
}
renderApp()
这里唯一的额外更改是我们已将应用程序的渲染封装到一个新的 renderApp
函数中,我们现在调用它来重新渲染应用程序。
使用 Redux Toolkit 简化设置
Redux 核心库故意不加任何意见。它允许您决定如何处理所有内容,例如存储设置、状态包含的内容以及如何构建 reducer。
在某些情况下这是好事,因为它给了您灵活性,但这种灵活性并不总是需要的。有时我们只想获得最简单的入门方法,并提供一些开箱即用的良好默认行为。
Redux Toolkit 包旨在帮助简化一些常见的 Redux 用例,包括存储设置。让我们看看它如何帮助改进存储设置过程。
Redux Toolkit 包含一个预构建的 configureStore
函数,类似于之前示例中展示的。
使用它的最快方法是直接传递根 reducer 函数。
import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducers'
const store = configureStore({
reducer: rootReducer
})
export default store
请注意,它接受一个带有命名参数的对象,以使您传递的内容更清晰。
默认情况下,来自 Redux Toolkit 的 configureStore
将
- 使用 默认中间件列表(包括
redux-thunk
) 和一些仅在开发环境中使用的中间件(用于捕获常见的错误,例如修改状态)调用applyMiddleware
- 调用
composeWithDevTools
来设置 Redux DevTools 扩展
以下是使用 Redux Toolkit 的热重载示例可能的样子
import { configureStore } from '@reduxjs/toolkit'
import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'
export default function configureAppStore(preloadedState) {
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware().prepend(loggerMiddleware),
preloadedState,
enhancers: [monitorReducersEnhancer]
})
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
}
return store
}
这绝对简化了一些设置过程。
下一步
现在您已经了解了如何封装存储配置以使其更易于维护,您可以 查看 Redux Toolkit configureStore
API,或更仔细地查看一些 Redux 生态系统中可用的扩展。