本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
配置 Store
在 《Redux 基础教程》中,我们通过构建一个待办事项应用示例介绍了 Redux 的核心概念。其中包含了如何创建和配置 Redux store的内容。
现在我们将探索如何定制 store 来添加额外功能。我们将基于《Redux 基础教程》第 5 部分:UI 与 React的代码继续开发。您可以在Github 示例仓库查看此阶段的代码,或通过CodeSandbox 在线查看。
创建 Store
首先,查看我们最初创建 store 的 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')
)
在这段代码中,我们将 reducers 传递给 Redux 的 createStore 函数,该函数返回一个 store 对象。然后我们将此对象传递给 react-redux 的 Provider 组件,该组件被渲染在组件树的顶部。
这确保了当我们通过 react-redux 的 connect 连接到 Redux 时,store 对组件可用。
扩展 Redux 功能
大多数应用通过添加中间件(middleware)或 store 增强器(enhancer)来扩展 Redux store 的功能(注:中间件很常见,增强器较少见)。中间件为 Redux dispatch 函数添加额外功能;增强器为 Redux store 添加额外功能。
我们将添加两个中间件和一个增强器:
-
redux-thunk中间件,允许简单的异步 dispatch -
记录已 dispatch action 和更新后状态的中间件
-
记录 reducer 处理每个 action 耗时的增强器
安装 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创建一个 store 增强器,将loggerMiddleware和thunk中间件应用到 store 的 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 { thunk } 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, thunk)
const composedEnhancers = compose(middlewareEnhancer, monitorReducerEnhancer)
const store = createStore(rootReducer, undefined, composedEnhancers)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
此方法存在的问题
虽然这段代码可以工作,但对典型应用来说并不理想
大多数应用使用多个中间件,且每个中间件通常需要初始化配置。这些额外代码会使 index.js 快速变得难以维护,因为逻辑没有被清晰组织
解决方案:configureStore
解决方案是创建新的 configureStore 函数来封装 store 创建逻辑,然后将其移至独立文件以便扩展
最终目标是让 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')
)
所有与配置 store 相关的逻辑——包括导入 reducers、中间件和增强器——都在专用文件中处理
为实现此目标,configureStore 函数如下:
import { applyMiddleware, compose, createStore } from 'redux'
import { thunk } 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, thunk]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = compose(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
return store
}
此函数遵循上述步骤,部分逻辑被拆分以便未来扩展:
-
middlewares和enhancers被定义为数组,与消费它们的函数分离这使得我们能根据条件轻松添加更多中间件或增强器
例如,常见做法是在开发模式下添加某些中间件,这可以通过在 if 语句中向中间件数组添加元素实现:
if (process.env.NODE_ENV === 'development') {
middlewares.push(secretMiddleware)
} -
preloadedState变量被传递给createStore以备后用
这也使 createStore 函数更易理解——每个步骤清晰分离,明确展示了具体执行过程
集成开发者工具扩展
另一个可能需要添加到应用的功能是 redux-devtools-extension 集成
该扩展是一套提供对 Redux store 完全控制的工具——允许检查和重放 action、在不同时间点检查状态、直接向 store dispatch action 等。点击此处了解可用功能
有几种集成方式,我们将使用最便捷的方案
首先通过 npm 安装包:
npm install --save-dev redux-devtools-extension
接着移除从 redux 导入的 compose 函数,改用 redux-devtools-extension 导入的 composeWithDevTools 函数
最终代码如下:
import { applyMiddleware, createStore } from 'redux'
import { thunk } 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, thunk]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = composeWithDevTools(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
return store
}
这样就完成了!
如果现在通过安装了开发者工具扩展的浏览器访问应用,就可以使用这个强大的新工具进行探索和调试
热重载
另一个能显著提升开发效率的强大工具是热重载(hot reloading),它允许在不重启整个应用的情况下替换代码片段
例如:当您运行应用并进行交互后,决定修改某个 reducer。通常修改后应用会重启,Redux 状态将重置为初始值
启用热模块重载后,只有被修改的 reducer 会被重新加载,从而允许您更改代码而无需每次都重置状态。这大大加快了开发流程
我们将为 Redux reducers 和 React 组件同时添加热重载
首先在 configureStore 函数中添加:
import { applyMiddleware, compose, createStore } from 'redux'
import { thunk } 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, thunk]
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 将自动:
-
调用
applyMiddleware并加载默认中间件列表(含redux-thunk),以及用于检测状态突变等常见错误的开发环境专用中间件 -
调用
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 生态系统中可用的扩展功能。