配置你的存储
¥Configuring Your Store
在 "Redux 基础知识" 教程 中,我们通过构建示例 Todo 列表应用介绍了基本的 Redux 概念。作为其中的一部分,我们讨论了 如何创建和配置 Redux 存储。
¥In the "Redux Fundamentals" tutorial, we introduced the fundamental Redux concepts by building an example Todo list app. As part of that, we talked about how to create and configure a Redux store.
我们现在将探讨如何自定义存储以添加额外的功能。我们将从 "Redux 基础知识" 第 5 部分:UI 和 React 的源代码开始。你可以在 Github 上的示例应用存储库 或 通过 CodeSandbox 在浏览器中 中查看本阶段教程的源代码。
¥We will now explore how to customise the store to add extra functionality. We'll start with the source code from "Redux Fundamentals" part 5: UI and React. You can view the source from this stage of the tutorial in the example app repository on Github, or in your browser via CodeSandbox.
创建存储
¥Creating the store
首先,让我们看一下我们在其中创建存储的原始 index.js
文件:
¥First, let's look at the original index.js
file in which we created our store:
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
组件,该组件渲染在组件树的顶部。
¥In this code, we pass our reducers to the Redux createStore
function, which returns a store
object. We then pass this object to the react-redux
Provider
component, which is rendered at the top of our component tree.
这确保了每当我们通过 react-redux
connect
连接到应用中的 Redux 时,该存储都可供我们的组件使用。
¥This ensures that any time we connect to Redux in our app via react-redux
connect
, the store is available to our components.
扩展 Redux 功能
¥Extending Redux functionality
大多数应用通过添加中间件或存储增强器来扩展 Redux 存储的功能(注意:中间件很常见,增强器不太常见)。中间件在 Redux dispatch
功能的基础上增加了额外的功能;增强器为 Redux 存储添加额外的功能。
¥Most apps extend the functionality of their Redux store by adding middleware or store enhancers (note: middleware is common, enhancers are less common). Middleware adds extra functionality to the Redux dispatch
function; enhancers add extra functionality to the Redux store.
我们将添加两个中间件和一个增强器:
¥We will add two middlewares and one enhancer:
redux-thunk
中间件,允许简单地异步使用调度。¥The
redux-thunk
middleware, which allows simple asynchronous use of dispatch.记录分派操作和生成的新状态的中间件。
¥A middleware which logs dispatched actions and the resulting new state.
一个增强器,记录 reducer 处理每个动作所花费的时间。
¥An enhancer which logs the time taken for the reducers to process each action.
安装 redux-thunk
¥Install 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
中。
¥Let's add these to our existing index.js
.
首先,我们需要导入
redux-thunk
加上我们的loggerMiddleware
和monitorReducerEnhancer
,再加上 Redux 提供的两个额外函数:applyMiddleware
和compose
。¥First, we need to import
redux-thunk
plus ourloggerMiddleware
andmonitorReducerEnhancer
, plus two extra functions provided by Redux:applyMiddleware
andcompose
.然后,我们使用
applyMiddleware
创建一个存储增强器,它将把loggerMiddleware
和thunkMiddleware
应用到存储的调度函数。¥We then use
applyMiddleware
to create a store enhancer which will apply ourloggerMiddleware
and thethunkMiddleware
to the store's dispatch function.接下来,我们使用
compose
将新的middlewareEnhancer
和monitorReducerEnhancer
组合成一个函数。¥Next, we use
compose
to compose our newmiddlewareEnhancer
and ourmonitorReducerEnhancer
into one function.这是必需的,因为你只能将一个增强子传递到
createStore
中。要使用多个增强器,你必须首先将它们组合成一个更大的增强器,如此例所示。¥This is needed because you can only pass one enhancer into
createStore
. To use multiple enhancers, you must first compose them into a single larger enhancer, as shown in this example.最后,我们将这个新的
composedEnhancers
函数传递给createStore
作为其第三个参数。注意:我们将忽略第二个参数,它允许你将状态预加载到存储中。¥Finally, we pass this new
composedEnhancers
function intocreateStore
as its third argument. Note: the second argument, which we will ignore, lets you preloaded state into the store.
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')
)
这种方法的问题
¥Problems with this approach
虽然此代码有效,但对于典型的应用来说它并不理想。
¥While this code works, for a typical app it is not ideal.
大多数应用使用多个中间件,并且每个中间件通常都需要一些初始设置。添加到 index.js
的额外噪声很快就会使其难以维护,因为逻辑组织不清晰。
¥Most apps use more than one middleware, and each middleware often requires some initial setup. The extra noise added to the index.js
can quickly make it hard to maintain, because the logic is not cleanly organised.
解决方案:configureStore
¥The solution: configureStore
这个问题的解决方案是创建一个新的 configureStore
函数,它封装了我们的存储创建逻辑,然后可以将其放置在自己的文件中以简化可扩展性。
¥The solution to this problem is to create a new configureStore
function which encapsulates our store creation logic, which can then be located in its own file to ease extensibility.
最终目标是我们的 index.js
看起来像这样:
¥The end goal is for our index.js
to look like this:
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、中间件和增强器 - 在专用文件中处理。
¥All the logic related to configuring the store - including importing reducers, middleware, and enhancers - is handled in a dedicated file.
为了实现这一点,configureStore
函数如下所示:
¥To achieve this, configureStore
function looks like this:
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
}
该函数遵循上面概述的相同步骤,并拆分了一些逻辑以准备扩展,这将使将来更容易添加更多内容:
¥This function follows the same steps outlined above, with some of the logic split out to prepare for extension, which will make it easier to add more in future:
middlewares
和enhancers
都定义为数组,与使用它们的函数分开。¥Both
middlewares
andenhancers
are defined as arrays, separate from the functions which consume them.这使我们可以根据不同的情况轻松添加更多中间件或增强器。
¥This allows us to easily add more middleware or enhancers based on different conditions.
例如,通常仅在开发模式下添加一些中间件,这可以通过推送到 if 语句内的 middlewares 数组来轻松实现:
¥For example, it is common to add some middleware only when in development mode, which is easily achieved by pushing to the middlewares array inside an if statement:
if (process.env.NODE_ENV === 'development') {
middlewares.push(secretMiddleware)
}preloadedState
变量被传递到createStore
,以防我们稍后想添加它。¥A
preloadedState
variable is passed through tocreateStore
in case we want to add this later.
这也使得我们的 createStore
函数更容易推断 - 每个步骤都清晰分开,这使得到底发生了什么更加明显。
¥This also makes our createStore
function easier to reason about - each step is clearly separated, which makes it more obvious what exactly is happening.
集成开发工具扩展
¥Integrating the devtools extension
你可能希望添加到应用中的另一个常见功能是 redux-devtools-extension
集成。
¥Another common feature which you may wish to add to your app is the redux-devtools-extension
integration.
该扩展是一套工具,可让你绝对控制 Redux 存储 - 它允许你检查和重放操作、探索不同时间的状态、将操作直接发送到存储等等。单击此处了解有关可用功能的更多信息。
¥The extension is a suite of tools which give you absolute control over your Redux store - it allows you to inspect and replay actions, explore your state at different times, dispatch actions directly to the store, and much more. Click here to read more about the available features.
集成扩展的方法有多种,但我们将使用最方便的选项。
¥There are several ways to integrate the extension, but we will use the most convenient option.
首先,我们通过 npm 安装软件包:
¥First, we install the package via npm:
npm install --save-dev redux-devtools-extension
接下来,我们删除从 redux
导入的 compose
函数,并将其替换为从 redux-devtools-extension
导入的新 composeWithDevTools
函数。
¥Next, we remove the compose
function which we imported from redux
, and replace it with a new composeWithDevTools
function imported from redux-devtools-extension
.
最终代码如下所示:
¥The final code looks like this:
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
}
就是这样!
¥And that's it!
如果我们现在通过安装了 devtools 扩展的浏览器访问我们的应用,我们可以使用功能强大的新工具进行探索和调试。
¥If we now visit our app via a browser with the devtools extension installed, we can explore and debug using a powerful new tool.
热重载
¥Hot reloading
另一个可以使开发过程更加直观的强大工具是热重载,这意味着无需重新启动整个应用即可替换代码片段。
¥Another powerful tool which can make the development process a lot more intuitive is hot reloading, which means replacing pieces of code without restarting your whole app.
例如,考虑一下当你运行应用、与其交互一段时间,然后决定对其中一个 reducer 进行更改时会发生什么。通常,当你进行这些更改时,你的应用将重新启动,将 Redux 状态恢复为其初始值。
¥For example, consider what happens when you run your app, interact with it for a while, and then decide to make changes to one of your reducers. Normally, when you make those changes your app will restart, reverting your Redux state to its initial value.
启用热模块重新加载后,只有你更改的 reducer 才会被重新加载,这样你就可以更改代码而无需每次都重置状态。这使得开发过程变得更快。
¥With hot module reloading enabled, only the reducer you changed would be reloaded, allowing you to change your code without resetting the state every time. This makes for a much faster development process.
我们将为 Redux reducer 和 React 组件添加热重载。
¥We'll add hot reloading both to our Redux reducers and to our React components.
首先,我们将其添加到 configureStore
函数中:
¥First, let's add it to our configureStore
function:
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
功能可用时运行。
¥The new code is wrapped in an if
statement, so it only runs when our app is not in production mode, and only if the module.hot
feature is available.
像 Webpack 和 Parcel 这样的打包器支持 module.hot.accept
方法来指定哪个模块应该热重载,以及模块更改时应该发生什么。在本例中,我们正在监视 ./reducers
模块,并在其更改时将更新的 rootReducer
传递给 store.replaceReducer
方法。
¥Bundlers like Webpack and Parcel support a module.hot.accept
method to specify which module should be hot reloaded, and what should happen when the module changes. In this case, we're watching the ./reducers
module, and passing the updated rootReducer
to the store.replaceReducer
method when it changes.
我们还将在 index.js
中使用相同的模式来热重新加载对 React 组件的任何更改:
¥We'll also use the same pattern in our index.js
to hot reload any changes to our React components:
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
函数中,我们现在调用该函数来重新渲染应用。
¥The only extra change here is that we have encapsulated our app's rendering into a new renderApp
function, which we now call to re-render the app.
使用 Redux Toolkit 简化设置
¥Simplifying Setup with Redux Toolkit
Redux 核心库是故意不带任何意见的。它可以让你决定如何处理所有事情,例如存储设置、状态包含什么以及如何构建 reducer。
¥The Redux core library is deliberately unopinionated. It lets you decide how you want to handle everything, like store setup, what your state contains, and how you want to build your reducers.
在某些情况下这很好,因为它为你提供了灵活性,但并不总是需要这种灵活性。有时我们只想以最简单的方式开始,并提供一些开箱即用的良好默认行为。
¥This is good in some cases, because it gives you flexibility, but that flexibility isn't always needed. Sometimes we just want the simplest possible way to get started, with some good default behavior out of the box.
Redux 工具包 包旨在帮助简化几个常见的 Redux 用例,包括存储设置。让我们看看它如何帮助改进存储设置流程。
¥The Redux Toolkit package is designed to help simplify several common Redux use cases, including store setup. Let's see how it can help improve the store setup process.
Redux Toolkit 包含一个预构建的 configureStore
功能,如前面示例中所示。
¥Redux Toolkit includes a prebuilt configureStore
function like
the one shown in the earlier examples.
最快的使用方法是只传递根 reducer 函数:
¥The fastest way to use is it is to just pass the root reducer function:
import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducers'
const store = configureStore({
reducer: rootReducer
})
export default store
请注意,它接受带有命名参数的对象,以使你传入的内容更加清晰。
¥Note that it accepts an object with named parameters, to make it clearer what you're passing in.
默认情况下,Redux Toolkit 中的 configureStore
将:
¥By default, configureStore
from Redux Toolkit will:
用 默认中间件列表,包括
redux-thunk
调用applyMiddleware
,以及一些仅用于开发的中间件,可以捕获常见错误(例如状态变化)¥Call
applyMiddleware
with a default list of middleware, includingredux-thunk
, and some development-only middleware that catch common mistakes like mutating state调用
composeWithDevTools
设置 Redux DevTools 扩展¥Call
composeWithDevTools
to set up the Redux DevTools Extension
使用 Redux Toolkit 的热重载示例如下:
¥Here's what the hot reloading example might look like using 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
}
这无疑简化了一些设置过程。
¥That definitely simplifies some of the setup process.
下一步
¥Next Steps
现在你已经知道如何封装你的存储配置以使其更易于维护,你可以 查看 Redux 工具包 configureStore
API,或者仔细查看一些 Redux 生态系统中可用的扩展。
¥Now that you know how to encapsulate your store configuration to make it easier to maintain, you can look at the Redux Toolkit configureStore
API, or take a closer look at some of the extensions available in the Redux ecosystem.