Redux Store 完全指南:创建、配置、中间件与 DevTools 调试
【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux
本篇技术指南聚焦 Redux 应用的核心枢纽——Store,完整讲解如何通过createStore创建单一 store、如何加载初始状态、如何用dispatch/subscribe/getState驱动状态流转,并深入 store 增强器(Enhancer)与中间件(Middleware)两大扩展机制,最后接入 Redux DevTools 进行可视化调试。文中所有原理讲解均结合本仓库(reduxjs/redux)的 TypeScript 源码与测试用例给出可验证依据,读完你不仅能熟练搭建一个可运行的 Redux store,还能从源码层面理解其内部工作机制,为后续编写异步逻辑(thunk)打下基础。
Redux Store:应用状态的中央枢纽
在前序章节 Redux Fundamentals Part 3:State、Actions 与 Reducers 中,我们定义了状态结构、编写了 action type、实现了 reducer,并用combineReducers组装出根 reducer。现在,是时候把这些碎片拼装成 Redux 应用的"心脏"——store。
store 把 state、actions 和 reducers 三者联结在一起,承担以下职责:
- 持有当前应用状态;
- 通过
store.getState()允许外部读取当前状态; - 通过
store.dispatch(action)允许更新状态; - 通过
store.subscribe(listener)注册监听回调; - 通过
store.subscribe(listener)返回的unsubscribe函数注销监听器。
官方 API 文档将 store 精炼地描述为:"store 持有整个应用的状态树,改变其中状态的唯一方式就是 dispatch 一个 action 到 store 上,从而触发根 reducer 计算新状态"(见 docs/api/Store.md)。值得强调的一点是:store 不是一个类(class),它只是一个带有若干方法的普通对象。
一个 Redux 应用永远只有一个 store。当你需要拆分数据处理逻辑时,应当使用 reducer 组合(reducer composition)创建多个可合并的 slice reducer,而不是创建多个 store(详见 Part 3 的 reducer 拆分章节)。这一点在 createStore API 文档 中同样被列为第一条使用提示:"不要在一个应用中创建多个 store!"
从本仓库的 TypeScript 源码 src/index.ts 可以看到,redux 包对外导出的核心函数正是createStore、legacy_createStore、combineReducers、bindActionCreators、applyMiddleware、compose等,store 相关的Dispatch、Unsubscribe、Store、StoreEnhancer等类型也一并导出供类型推断使用。
创建第一个 Store
每个 Redux store 都对应一个单一的根 reducer 函数。前一步我们用combineReducers创建了根 reducer(在示例应用中位于src/reducer.js)。现在新建store.js文件,导入 redux 核心库的createStoreAPI 与根 reducer,然后调用createStore(rootReducer):
import { createStore } from 'redux' import rootReducer from './reducer' const store = createStore(rootReducer) export default storecreateStore的完整签名是createStore(reducer, preloadedState?, enhancer?)(见 docs/api/createStore.md):
| 参数 | 类型 | 说明 |
|---|---|---|
reducer | Function | 根 reducer 函数,接收当前状态树与 action,返回下一个状态树 |
preloadedState | any(可选) | 初始状态,用于 SSR 时从服务端水合,或恢复序列化的用户会话 |
enhancer | Function(可选) | store 增强器,用于扩展第三方能力,如中间件、时间旅行、持久化等 |
createStore 的源码级校验逻辑
深入本仓库的 src/createStore.ts,可以看到createStore在真正创建 store 之前做了严格的参数校验:
- reducer 必须是函数:否则抛出
Expected the root reducer to be a function. Instead, received: '...'; - 不允许传入多个 enhancer:如果
preloadedState和enhancer同时是函数,或第二个、第三个参数都是函数,会抛出错误并提示"你应该用compose把它们合并成单一函数"; - 支持省略 preloadedState:当第二个参数是函数而第三个参数为
undefined时,源码会自动把第二个参数当作 enhancer 处理(enhancer = preloadedState; preloadedState = undefined),这正是文档中"没有 preloadedState 时可以直接把 enhancer 作为第二个参数"这一技巧的底层实现; - enhancer 必须是函数:否则抛出
Expected the enhancer to be a function. Instead, received: '...'。
当 enhancer 存在时,源码执行return enhancer(createStore)(reducer, preloadedState)——enhancer 接收原始createStore并返回一个"增强版"的 store 创建器,这就是 enhancer 包装机制的起点。
关于 createStore 的弃用说明
需要特别说明的是:自 Redux 4.2.0 起,核心库已将createStore标记为@deprecated(仅在类型层面显示删除线,不会产生任何运行时错误或警告),官方强烈建议新代码使用 Redux Toolkit 的configureStore,它包装了createStore并提供了更友好的默认配置(详见 docs/api/createStore.md 与 migrating-to-modern-redux.mdx)。createStore本身不会被移除;如果你不想看到 IDE 中的删除线提示,可以改用等价导出的legacy_createStore(源码见 src/createStore.ts,它只是简单转发到createStore)。本文档按官方教程的脉络继续使用createStore来讲解核心原理,这完全适用于学习场景。
加载初始状态(preloadedState)
createStore的第二个参数preloadedState允许你在 store 创建时注入初始数据。常见场景包括:读取服务端 HTML 页面中内联的数据、从localStorage恢复上次的持久化状态。例如:
import { createStore } from 'redux' import rootReducer from './reducer' let preloadedState const persistedTodosString = localStorage.getItem('todos') if (persistedTodosString) { preloadedState = { todos: JSON.parse(persistedTodosString) } } const store = createStore(rootReducer, preloadedState)使用preloadedState时有几个关键约束(见 docs/api/createStore.md):
- 如果你用
combineReducers生成根 reducer,preloadedState必须是与combineReducers键结构一致的普通对象(否则各 slice reducer 拿不到对应的初始切片);否则你可以传入任何你的 reducer 能理解的数据; - 创建 store 时 Redux 会向 reducer派发一个内部占位 action(INIT)来填充初始状态树,你不需要也不应该直接处理这个 action,只需保证 reducer 在 state 为
undefined时能返回合适的初始状态即可。
派发 Action:让状态动起来
store 创建好后,即使没有 UI,我们也能立刻验证整个更新逻辑。首先建议把src/features/todos/todosSlice.js中initialState里的示例 todo 清空为空数组,这样控制台输出更清晰。然后在src/index.js中依次调用 store 的三个方法:
// Omit existing React imports import store from './store' // Log the initial state console.log('Initial state: ', store.getState()) // {todos: [....], filters: {status, colors}} // Every time the state changes, log it // Note that subscribe() returns a function for unregistering the listener const unsubscribe = store.subscribe(() => console.log('State after dispatch: ', store.getState()) ) // Now, dispatch some actions store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' }) store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about reducers' }) store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about stores' }) store.dispatch({ type: 'todos/todoToggled', payload: 0 }) store.dispatch({ type: 'todos/todoToggled', payload: 1 }) store.dispatch({ type: 'filters/statusFilterChanged', payload: 'Active' }) store.dispatch({ type: 'filters/colorFilterChanged', payload: { color: 'red', changeType: 'added' } }) // Stop listening to state updates unsubscribe() // Dispatch one more action to see what happens store.dispatch({ type: 'todos/todoAdded', payload: 'Try creating a store' }) // Omit existing React rendering logic每次调用store.dispatch(action)时,会发生以下事情:
- store 调用
rootReducer(state, action); - 根 reducer 内部可能继续调用各个 slice reducer(如
todosReducer(state.todos, action)); - store 保存新的 state 值;
- store 依次调用所有订阅的监听回调;
- 监听器(若持有 store 引用)此时可调用
store.getState()读取最新状态。
运行上面的代码,控制台会逐条打印出 dispatch 后的状态变化:
注意最后一次 action 没有任何输出——因为我们已经调用了unsubscribe()移除了监听器。在写任何 UI 之前,我们就已经验证了应用的行为,这为后续开发建立了信心。
dispatch 在源码中如何工作
对照 src/createStore.ts 中的dispatch实现,可以看到核心循环非常简洁:
try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } const listeners = (currentListeners = nextListeners) listeners.forEach(listener => { listener() }) return action同时源码在派发前做了三道防御性校验,这些校验逻辑在 test/createStore.spec.ts 中均有对应测试覆盖:
- action 必须是纯对象:通过
isPlainObject检查,否则抛出Actions must be plain objects...并提示你可能需要中间件(如redux-thunk)来处理函数类型的 action; - action.type 不能是
undefined:提示你可能拼错了 action type 字符串常量; - action.type 必须是字符串:推荐用可序列化的字符串而非 Symbol 作为 type;
- reducer 执行期间禁止再次 dispatch:通过
isDispatching标志位保证,否则抛出Reducers may not dispatch actions.——reducer 是纯函数,只能返回新状态,绝不能有副作用。
dispatch的返回值是同一个 action 对象(方便起见);文档 docs/api/Store.md 进一步补充:如果使用自定义中间件包装了 dispatch,它可能返回其他值(例如一个可 await 的 Promise)。
subscribe 的细节与陷阱
subscribe(listener)注册的监听器会在每次 dispatch 之后被调用(无论状态是否真的变化),你可以在回调里调用getState()读取最新状态。需要注意(见 docs/api/Store.md 与源码注释):
- 订阅列表在每次 dispatch 前被快照:如果在监听器执行期间进行 subscribe/unsubscribe,不会影响正在进行的这一次 dispatch,但下一次 dispatch 会使用新的快照;
- 监听器不应期待看到所有状态变化:嵌套 dispatch 时状态可能已被多次更新;
- 监听器中可以 dispatch(订阅是在根 reducer 返回新状态之后才被调用的),但不加条件地 dispatch 会陷入无限循环;
- 注销监听的方式是调用
subscribe返回的函数。
源码中订阅列表使用Map<number, ListenerCallback>存储,并通过ensureCanMutateNextListeners()做浅拷贝快照(src/createStore.ts),这正是文档所述"快照机制"的实现细节。此外 store 还暴露了replaceReducer(nextReducer)(用于代码分割或热重载)与observable()(Observable 互操作接口)两个高级方法;test/createStore.spec.ts 中"exposes the public API"测试确认了 store 对象恰好暴露subscribe、dispatch、getState、replaceReducer四个公开方法。
顺手为 reducer 写测试
因为 reducer 是纯函数,测试它们非常直接:给定示例state与action,断言返回值是否符合预期:
import todosReducer from './todosSlice' test('Toggles a todo based on id', () => { const initialState = [{ id: 0, text: 'Test text', completed: false }] const action = { type: 'todos/todoToggled', payload: 0 } const result = todosReducer(initialState, action) expect(result[0].completed).toBe(true) })深入 Store 内部:25 行迷你实现
理解 store 最快的方式,是亲手"拆开"它看看。下面是一个约 25 行的可工作迷你版 Redux store:
function createStore(reducer, preloadedState) { let state = preloadedState const listeners = [] function getState() { return state } function subscribe(listener) { listeners.push(listener) return function unsubscribe() { const index = listeners.indexOf(listener) listeners.splice(index, 1) } } function dispatch(action) { state = reducer(state, action) listeners.forEach(listener => listener()) } dispatch({ type: '@@redux/INIT' }) return { dispatch, subscribe, getState } }这个小实现足以替换你应用中正在使用的真实createStore(不妨亲自试试)。真实的 src/createStore.ts 实现更长更复杂,但多出的部分主要是注释、警告信息、参数校验与边界情况处理,核心逻辑与迷你版一一对应:
- store 内部持有当前
state值与reducer函数; getState返回当前状态值;subscribe维护一个监听器数组并返回移除该监听器的函数;dispatch调用 reducer、保存新状态、然后执行所有监听器;- store 启动时会派发一个初始化 action,让所有 reducer 返回各自的初始状态;
- store 的公开 API 就是一个
{dispatch, subscribe, getState}对象。
关于启动时的初始化 action,真实源码中它是带随机后缀的@@redux/INIT(见 src/utils/actionTypes.ts:INIT: '@@redux/INIT' + randomString(),还有REPLACE与PROBE_UNKNOWN_ACTION两个内部保留类型),以保证不会被业务代码意外匹配。
getState 不会保护你:小心意外突变
特别强调一点:getState只是把当前的state值原样返回。这意味着默认情况下没有任何机制阻止你意外地突变当前状态!下面这段代码不会报错,但它是错误的:
const state = store.getState() // ❌ Don't do this - it mutates the current state! state.filters.status = 'Active'换句话说:
- store 在
getState()时不会复制状态,返回的就是根 reducer 返回的那个引用本身; - store 也不会做任何额外防护来阻止突变——无论是在 reducer 内部还是外部,你都可能突变状态,必须时刻警惕。
一个常见的意外突变来源是数组排序:array.sort()会就地修改原数组。如果写成const sortedTodos = state.todos.sort(),就会无意中改掉 store 里的真实状态。在 Part 8: Modern Redux 中你会看到 Redux Toolkit 如何帮助避免 reducer 内的突变,并在 reducer 之外检测并警告意外突变。
配置 Store:Store Enhancer
除了rootReducer和preloadedState,createStore还接受第三个参数,用于定制 store 的能力。这个参数就是store enhancer——一种特殊的createStore版本,在原始 store 外包一层新逻辑。增强后的 store 可以提供自己的dispatch、getState、subscribe版本来改变 store 行为。
本教程不深入 enhancer 的实现细节,重点看如何使用。示例项目在src/exampleAddons/enhancers.js中提供了两个小 enhancer:
sayHiOnDispatch:每次派发 action 时向控制台打印'Hi!';includeMeaningOfLife:每次调用getState()时向返回的状态里添加字段meaningOfLife: 42。
使用单个 Enhancer
先使用sayHiOnDispatch:导入它并传给createStore:
import { createStore } from 'redux' import rootReducer from './reducer' import { sayHiOnDispatch } from './exampleAddons/enhancers' const store = createStore(rootReducer, undefined, sayHiOnDispatch) export default store这里没有preloadedState,所以第二个参数传undefined。然后派发一个 action:
import store from './store' console.log('Dispatching action') store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' }) console.log('Dispatch complete')控制台会看到'Hi!'出现在两句日志之间:
原理是:sayHiOnDispatch用自己定制的dispatch包装了原始store.dispatch。我们调用的store.dispatch()实际是包装函数,它先调用原始 dispatch,再打印'Hi'。
用 compose 合并多个 Enhancer
现在再加第二个 enhancerincludeMeaningOfLife——但问题来了:createStore第三个参数只接受一个 enhancer!怎样同时传入两个?
答案是 Redux 核心自带的compose函数(API 见 docs/api/compose.md),它能把多个 enhancer 合并成一个:
import { createStore, compose } from 'redux' import rootReducer from './reducer' import { sayHiOnDispatch, includeMeaningOfLife } from './exampleAddons/enhancers' const composedEnhancer = compose(sayHiOnDispatch, includeMeaningOfLife) const store = createStore(rootReducer, undefined, composedEnhancer) export default store使用效果:
import store from './store' store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' }) // log: 'Hi!' console.log('State after dispatch: ', store.getState()) // log: {todos: [...], filters: {status, colors}, meaningOfLife: 42}可以看到两个 enhancer 同时生效:sayHiOnDispatch改变了dispatch的行为,includeMeaningOfLife改变了getState的行为。几乎所有的 Redux 应用在搭建 store 时都会包含至少一个 enhancer,这是非常强大的扩展手段。
从 src/compose.ts 源码看,compose(...funcs)的实现是funcs.reduce((a, b) => (...args) => a(b(...args)))——即从右向左组合单参数函数,compose(f, g, h)等价于(...args) => f(g(h(...args)));传入 0 个函数时返回恒等函数,传入 1 个函数时直接返回该函数。这也解释了为什么组合顺序会影响增强效果:最右侧的 enhancer 最先执行(最靠近原始 store)。
技巧:省略 preloadedState
如果你没有任何preloadedState要传,可以直接把 enhancer 作为第二个参数:
const store = createStore(rootReducer, storeEnhancer)如前面源码分析所述,createStore会自动识别"第二个参数是函数"的情况并把它当作 enhancer(src/createStore.ts)。
中间件(Middleware):定制 dispatch 的首选方式
Enhancer 很强大,因为它能覆写 store 的任意方法。但大多数时候,我们只需要定制dispatch的行为。Redux 用一类特殊的扩展——middleware(中间件)——来解决这个问题。
如果你用过 Express 或 Koa,对 middleware 一定不陌生:在这些框架里,中间件是插在"框架收到请求"与"框架生成响应"之间的代码,可用来添加 CORS 头、日志、压缩等能力,并且可以链式组合多个独立的第三方中间件。Redux 中间件解决的问题不同,但概念类似:Redux middleware 提供了"派发 action"与"action 到达 reducer"之间的第三方扩展点。人们用它做日志、崩溃上报、异步 API 通信、路由等。
与 reducer 不同,middleware 内部允许有副作用,包括 setTimeout 和各种异步逻辑。
使用中间件:applyMiddleware
Redux 中间件其实是在一个内置的特殊 store enhancer——applyMiddleware之上实现的。既然已经会添加 enhancer,现在直接使用它,并挂载三个示例中间件(来自项目的src/exampleAddons/middleware):
import { createStore, applyMiddleware } from 'redux' import rootReducer from './reducer' import { print1, print2, print3 } from './exampleAddons/middleware' const middlewareEnhancer = applyMiddleware(print1, print2, print3) // Pass enhancer as the second arg, since there's no preloadedState const store = createStore(rootReducer, middlewareEnhancer) export default store派发一个 action 试试:
import store from './store' store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' }) // log: '1' // log: '2' // log: '3'控制台输出:
中间件在 store 的dispatch方法周围形成一条管道。调用store.dispatch(action)时,实际调用的是管道中第一个中间件。该中间件可以随意处理 action(通常会像 reducer 一样检查 action.type 是否是自己关心的类型),不感兴趣就把它传给管道中的下一个中间件。在这个例子里,action 依次经过:
print1中间件(对外表现为store.dispatch)print2中间件print3中间件- 原始的
store.dispatch - store 内部的根 reducer
由于这些都是函数调用,它们从调用栈返回的顺序相反:print1最先执行、最后完成。
applyMiddleware 的源码实现
src/applyMiddleware.ts 的实现非常精炼,值得仔细阅读:
return createStore => (reducer, preloadedState) => { const store = createStore(reducer, preloadedState) let dispatch = () => { throw new Error('Dispatching while constructing your middleware is not allowed. ...') } const middlewareAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose<typeof dispatch>(...chain)(store.dispatch) return { ...store, dispatch } }要点如下:
- 每个中间件都接收
{ getState, dispatch }这个middlewareAPI(注意这里的dispatch是"惰性"包装,确保中间件构造期间不能 dispatch,否则会抛错); - 中间件链通过
compose(...chain)从右向左组合,最终包裹住原始的store.dispatch; - 返回值是一个以新
dispatch覆盖原方法的新 store 对象。
这也印证了 API 文档 docs/api/applyMiddleware.md 中的定义:中间件的签名是({ getState, dispatch }) => next => action。
编写自定义中间件
Redux 中间件是三层嵌套函数。先用 ES5 的function关键字写清楚结构:
// Middleware written as ES5 functions // Outer function: function exampleMiddleware(storeAPI) { return function wrapDispatch(next) { return function handleAction(action) { // Do anything here: pass the action onwards with next(action), // or restart the pipeline with storeAPI.dispatch(action) // Can also use storeAPI.getState() here return next(action) } } }拆解这三层函数及其参数:
exampleMiddleware(外层):这就是"中间件"本身,由applyMiddleware调用,接收包含{dispatch, getState}的storeAPI对象(与 store 上的dispatch、getState是同一个函数)。如果在这里调用storeAPI.dispatch(action),action 会重新从管道起点开始。该函数只被调用一次。wrapDispatch(中层):接收参数next——即管道中的下一个中间件。如果这是最后一个中间件,next就是原始的store.dispatch。调用next(action)把 action 传给下一个中间件。该函数也只被调用一次。handleAction(内层):接收当前action作为参数,每次派发 action 时都会被调用。
你可以随意命名,但按someCustomMiddleware/wrapDispatch/handleAction来命名有助于记忆各自职责。
因为都是普通函数,也可以用 ES2015 箭头函数写成更短的隐式返回形式(对不熟悉箭头函数的读者可能略难读):
const anotherExampleMiddleware = storeAPI => next => action => { // Do something in here, when each action is dispatched return next(action) }你的第一个自定义中间件:logger
假设想给应用加日志:控制台打印每个被派发的 action,以及 reducer 处理完后的新状态:
const loggerMiddleware = storeAPI => next => action => { console.log('dispatching', action) let result = next(action) console.log('next state', storeAPI.getState()) return result }每当 action 被派发:
handleAction前半段先执行,打印'dispatching';- 把 action 交给
next(可能是另一个中间件,也可能是真实的store.dispatch); - 最终 reducer 运行、状态更新,
next返回; - 此时调用
storeAPI.getState()就能看到新状态; - 最后返回
next中间件的result。
中间件可以改写 dispatch 的返回值
任何中间件都可以返回任意值,而管道中第一个中间件的返回值就是store.dispatch()的返回值。例如:
const alwaysReturnHelloMiddleware = storeAPI => next => action => { const originalResult = next(action) // Ignore the original result, return something else return 'Hello!' } const middlewareEnhancer = applyMiddleware(alwaysReturnHelloMiddleware) const store = createStore(rootReducer, middlewareEnhancer) const dispatchResult = store.dispatch({ type: 'some/action' }) console.log(dispatchResult) // log: 'Hello!'中间件与异步逻辑
中间件常常针对特定 action 做出响应,并且可以运行异步逻辑。例如,看到todos/todoAdded类型的 action 时延迟 1 秒打印 payload:
const delayedMessageMiddleware = storeAPI => next => action => { if (action.type === 'todos/todoAdded') { setTimeout(() => { console.log('Added a new todo: ', action.payload) }, 1000) } return next(action) }中间件的典型用途
看到派发的 action 时,中间件几乎可以做任何事:
- 向控制台打印日志
- 设置定时器
- 发起异步 API 调用
- 修改 action
- 暂停 action,甚至完全拦截它
- ……以及你能想到的任何其他事
特别是,中间件被设计为承载副作用逻辑;同时中间件可以修改dispatch,使其接受非纯对象 action(例如函数、Promise、Observable)。这两点将在 Part 6: Async Logic 中详细展开——届时你会看到redux-thunk正是通过中间件让 dispatch 接受函数(thunk)的。
Redux DevTools:可视化调试
Redux 的设计初衷之一,就是让"状态在何时、何地、为何、如何发生变化"变得易于理解。为此 Redux 从架构上支持Redux DevTools——一个展示 action 派发历史、action 内容以及每次派发后状态变化的浏览器插件。
安装扩展
Redux DevTools UI 以浏览器扩展的形式提供(Chrome 与 Firefox 均有)。安装后打开浏览器开发者工具,会看到一个新的 "Redux" 标签页——不过现在它还不会工作,因为 store 还没有与之建立连接。
接入 store:composeWithDevTools
DevTools 需要一个特定的 store enhancer。官方扩展文档中的步骤略显繁琐,好在redux-devtools-extension这个 npm 包封装了复杂的部分,它导出一个专用的composeWithDevTools函数,可替代原生的compose:
import { createStore, applyMiddleware } from 'redux' import { composeWithDevTools } from 'redux-devtools-extension' import rootReducer from './reducer' import { print1, print2, print3 } from './exampleAddons/middleware' const composedEnhancer = composeWithDevTools( // EXAMPLE: Add whatever middleware you actually want to use here applyMiddleware(print1, print2, print3) // other store enhancers if any ) const store = createStore(rootReducer, composedEnhancer) export default store确保index.js在导入 store 后仍会派发 action。然后打开浏览器开发者工具里的 Redux DevTools 标签页,你应该能看到类似下面的界面:
左侧是已派发 action 的列表;点击任意一个,右侧面板提供多个标签页:
- Action:该 action 对象的完整内容;
- State:reducer 运行之后的整个 Redux 状态;
- Diff:上一个状态与当前状态之间的差异;
- Trace(若启用):追溯到最初调用
store.dispatch()的代码行的函数调用栈。
下面是派发了 "add todo" action 后 "State" 与 "Diff" 标签页的展示效果:
这些工具能极大地帮助我们调试应用、精确理解 store 内部正在发生的事情。
关于中间件与 DevTools 的组合顺序
这里有一个值得注意的实践细节(见 docs/api/applyMiddleware.md):如果同时使用其他 enhancer,应把applyMiddleware放在组合链的前面(即作为composeWithDevTools的参数先传入),因为中间件可能是异步的——否则 DevTools 将看不到由 Promise 类中间件产生的原始 action。
小结
Store 是每个 Redux 应用的中心枢纽。它持有状态、通过 reducer 处理 action,并且可以被扩展以添加额外行为。本部分要点回顾:
- Redux 应用永远只有一个 store
- 通过
createStoreAPI 创建(新版推荐使用 Redux Toolkit 的configureStore) - 每个 store 对应一个单一的根 reducer 函数
- 通过
- store 有三个主要方法
getState:返回当前状态dispatch:把 action 交给 reducer 以更新状态subscribe:注册一个每次派发 action 后都会运行的监听回调,并返回用于注销的函数
- Store enhancer 让我们在创建时定制 store
- Enhancer 包装 store 并可以覆写其方法
createStore只接受一个 enhancer 参数- 多个 enhancer 可用
compose合并
- Middleware 是定制 store 的主要方式
- 通过
applyMiddlewareenhancer 添加 - 以三层嵌套函数的形式编写(
storeAPI => next => action => {...}) - 每次派发 action 时都会运行
- 内部允许副作用(定时器、异步 API 等)
- 通过
- Redux DevTools 让你看到应用随时间的变化
- 浏览器安装 DevTools 扩展
- 通过
composeWithDevTools为 store 添加 DevTools enhancer - 可以查看派发的 action 与状态随时间的演变(含 Diff 与 Trace)
下一步
现在我们已经有了一个可以运行 reducer、并在派发 action 时更新状态的 store。不过,任何应用都需要用户界面来展示数据、让用户执行有意义的操作。在 Part 5: UI 与 React 中,我们将看到 Redux store 如何与 UI 协作,尤其是 Redux 如何与 React 一起工作。
【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考