Redux 入门指南:安装、核心数据流与 createStore 源码级验证
本文基于 Redux 仓库官方 Getting Started 文档,系统讲解 Redux 的入门路径:如何安装 Redux Toolkit(RTK)与 Redux 核心包、如何用官方模板创建新项目、如何用 createSlice/configureStore 与 createStore 写出第一个计数器应用,并对照当前仓库的 src 源码逐一验证「store、action、reducer」三大数据流概念的真实实现,帮助读者既会写代码,也懂底层原理。
一、Redux 是什么:可预测的全局状态管理库
Redux 是一个用于可预测(predictable)且可维护(maintainable)的全局状态管理的 JavaScript 库。它帮助你编写行为一致、可运行于不同环境(浏览器、服务端、原生端)且易于测试的应用,并提供优秀的开发体验——例如结合实时代码编辑与时间旅行调试器(time traveling debugger)。
Redux 可以与 React 一起使用,也可以与任何其他视图库搭配。它非常小巧(约 2kB,含依赖),但拥有庞大的生态插件体系。
官方推荐立场(重要):Redux 官方明确推荐 Redux Toolkit(RTK) 作为编写 Redux 逻辑的首选方案。RTK 包裹在 Redux 核心之上,内置了官方建议的最佳实践,包含一系列构建 Redux 应用所必需的包与函数,它能简化大多数 Redux 任务、防止常见错误、降低编写 Redux 应用的难度。RTK 内置的实用工具覆盖了多种常见场景,包括:
- Store 配置:
configureStore封装了 store 搭建; - Reducer 与不可变更新:
createReducer帮助创建 reducer 并编写 immutable 更新逻辑; - 状态切片:
createSlice可以一次性创建整个状态切片(reducer + 对应 action creator)。
无论你是刚搭建第一个项目的 Redux 新手,还是希望简化既有应用的资深用户,RTK 都能让你的 Redux 代码更好。仓库中专门有一篇文档阐述「为什么 RTK 就是今天的 Redux」,见 why-rtk-is-redux-today.md。
二、安装
2.1 安装 Redux Toolkit
Redux Toolkit 以 NPM 包的形式发布,可用于模块打包器(Vite、Webpack 等)或 Node 应用中:
# NPM
npm install @reduxjs/toolkit
# Yarn
yarn add @reduxjs/toolkit
2.2 创建新的 Redux 项目
启动新项目时,官方推荐从官方模板入手。这些模板预配置好了 Redux Toolkit,并附带一个小的示例应用供你起步。可以用 tiged 这类工具来克隆并解压模板,文档给出的完整命令如下:
# Vite + TypeScript
npx tiged reduxjs/redux-templates/packages/vite-template-redux my-app
# Create React App + TypeScript
npx tiged reduxjs/redux-templates/packages/cra-template-redux-typescript my-app
# Create React App + JavaScript
npx tiged reduxjs/redux-templates/packages/cra-template-redux my-app
# Expo + TypeScript
npx tiged reduxjs/redux-templates/packages/expo-template-redux-typescript my-app
# React Native + TypeScript
npx tiged reduxjs/redux-templates/packages/react-native-template-redux-typescript my-app
# Standalone Redux Toolkit App Structure Example
npx tiged reduxjs/redux-templates/packages/rtk-app-structure-example my-app
除官方模板外,社区也提供了其他模板,例如 Next.js 的 with-redux 模板:
# Next.js + Redux
npx create-next-app --example with-redux my-app
更多安装细节(如 React-Redux 配套包、Redux DevTools 浏览器扩展的安装)可参见仓库内的 Installation 文档。
2.3 安装 Redux 核心包
Redux 核心库同样以 NPM 包发布,可用于模块打包器或 Node 应用:
# NPM
npm install redux
# Yarn
yarn add redux
从仓库根目录的 package.json 可以看到,当前发布的 redux 包版本为 5.0.1,它同时提供 CJS 与 ESM 构建产物:
"main": "dist/cjs/redux.cjs",
"module": "dist/redux.legacy-esm.js",
"exports": {
".": {
"types": "./dist/redux.d.mts",
"import": "./dist/redux.mjs",
"default": "./dist/cjs/redux.cjs"
}
}
并且声明了 "sideEffects": false,意味着打包器可以放心做 tree-shaking——这也是文档称其「tiny(2kB 级)」的构建层面原因。该包还包含一个预编译的 ESM 构建(redux.browser.mjs),可以直接在浏览器里通过 <script type="module"> 标签使用。仓库中的 examples/counter-vanilla/index.html 就是一个真实演示:它直接用 <script type="module"> 引入浏览器版 ESM 构建,定义一个 counter(state, action) reducer,然后 Redux.createStore(counter) 创建 store、store.subscribe(render) 订阅变化、点击按钮时 store.dispatch({ type: 'INCREMENT' }) 触发状态更新——无需任何打包器,完整验证了文档中「Basic Example」描述的最小可用闭环。
三、基础示例:三大核心概念(store / action / reducer)
Redux 的数据流规则可以概括为三句话:
- 应用的全部全局状态以对象树的形式存放在单一 store 中;
- 修改状态树的唯一方式是创建 action(一个描述「发生了什么」的对象)并将其 dispatch 到 store;
- 通过编写纯函数 reducer 来规定状态如何响应 action 更新:根据旧状态和 action 计算出新状态。
3.1 Redux Toolkit 写法
RTK 简化了编写 Redux 逻辑和配置 store 的过程。用 RTK 编写的基础应用逻辑如下(该片段与 examples/counter 示例项目的写法一致):
import { createSlice, configureStore } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
incremented: state => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decremented: state => {
state.value -= 1
}
}
})
export const { incremented, decremented } = counterSlice.actions
const store = configureStore({
reducer: counterSlice.reducer
})
// Can still subscribe to the store
store.subscribe(() => console.log(store.getState()))
// Still pass action objects to `dispatch`, but they're created for us
store.dispatch(incremented())
// {value: 1}
store.dispatch(incremented())
// {value: 2}
store.dispatch(decremented())
// {value: 1}
关键点:
state.value += 1这种「看似修改」的写法是安全的。RTK 底层使用 Immer 库:它检测对「draft state」的改动,并据此生成一个全新的 immutable 状态对象;createSlice一次性生成 reducer 和对应的 action creator(incremented()、decremented()),你仍然向dispatch传递 action 对象,只是这些对象由 RTK 替你创建;store.subscribe()与store.getState()的用法与核心 Redux 完全一致。
仓库中更完整的 RTK 切片示例见 examples/counter/src/features/counter/counterSlice.js:其中除了同步 reducers 之外,还展示了 extraReducers 处理 createAsyncThunk 生成的 pending/fulfilled 动作、selector(selectCount)以及手写 thunk(incrementIfOdd);而 examples/counter/src/app/store.js 则展示了应用长大后的标准 store 形态——configureStore({ reducer: { counter: counterReducer } }),即用 reducer map 而不是单个 reducer。
3.2 单一 store 与 reducer 拆分
在典型的 Redux 应用中,只有一个 store 和一个根 reducer 函数。随着应用增长,你把根 reducer 拆分为多个小 reducer,各自独立地处理状态树的不同部分——这与 React 应用中只有一个根组件、但由许多小组件组合而成完全类似。
「应用只有一个 store」这一点在核心源码中有明确体现。src/createStore.ts 的 JSDoc 写道:
The only way to change the data in the store is to call
dispatch()on it. There should only be a single store in your app. To specify how different parts of the state tree respond to actions, you may combine several reducers into a single reducer function by usingcombineReducers.
「拆分 reducer」对应仓库中的 src/combineReducers.ts。它在初始化时会执行 assertReducerShape:对每个 slice reducer 调用 reducer(undefined, { type: ActionTypes.INIT }),如果返回 undefined 就直接抛错——这就是 Redux 著名的「reducer 在 state 为 undefined 时必须显式返回初始状态」规则的强制点。文档 Basic Example 中 counterReducer(state = { value: 0 }, action) 给 state 设置默认参数,正是为了通过这一校验。
3.3 Legacy 写法(无抽象的原始 Redux)
作为对照,不使用任何抽象的原始 Redux 语法(legacy 写法)如下:
import { createStore } from 'redux'
/**
* This is a reducer - a function that takes a current state value and an
* action object describing "what happened", and returns a new state value.
* A reducer's function signature is: (state, action) => newState
*
* The Redux state should contain only plain JS objects, arrays, and primitives.
* The root state value is usually an object. It's important that you should
* not mutate the state object, but return a new object if the state changes.
*
* You can use any conditional logic you want in a reducer. In this example,
* we use a switch statement, but it's not required.
*/
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case 'counter/incremented':
return { value: state.value + 1 }
case 'counter/decremented':
return { value: state.value - 1 }
default:
return state
}
}
// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
let store = createStore(counterReducer)
// You can use subscribe() to update the UI in response to state changes.
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
// There may be additional use cases where it's helpful to subscribe as well.
store.subscribe(() => console.log(store.getState()))
// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
store.dispatch({ type: 'counter/incremented' })
// {value: 1}
store.dispatch({ type: 'counter/incremented' })
// {value: 2}
store.dispatch({ type: 'counter/decremented' })
// {value: 1}
与 RTK 版本相比,legacy 写法中:状态不能被直接修改,你只能用称为 action 的普通对象来「声明想发生的变更」;然后编写一个 reducer 函数,决定每个 action 如何转换整个应用状态。
3.4 为什么这种「看似繁琐」的架构值得
这个架构对于一个计数器应用来说可能显得多余,但它的价值在于能平滑扩展到大型复杂应用,并且能支撑非常强大的开发者工具——因为每一次状态变更都可以追溯到触发它的 action。你可以录制用户的操作会话,仅通过重放(replay)每一个 action 就能完整复现。Redux Toolkit 让你在保持相同 Redux 行为与数据流的前提下,写出更短、更易读的逻辑。
四、源码级验证:createStore 的 dispatch 校验与执行流程
文档中的三句话规则在 src/createStore.ts 中得到了逐条印证,以下摘录可直接对照阅读:
1. store 创建即派发一个 INIT action。创建 store 的最后一步是:
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT } as A)
也就是说,counterReducer 的初始值 { value: 0 } 正是在 store 构造时由这个内部 action「喂」给 reducer 得到的——这就是 reducer 默认参数 state = { value: 0 } 的意义。
2. dispatch 只接受普通对象 action,且 type 必须是字符串(src/createStore.ts):
function dispatch(action: A) {
if (!isPlainObject(action)) {
throw new Error(
`Actions must be plain objects. Instead, the actual type was: '${kindOf(action)}'. ...`
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
)
}
// ...type 必须为 string,且 reducer 执行期间不允许再 dispatch
这对应了文档中「action 是描述发生了什么的对象」这一约束:action 必须是 plain object(可用 src/utils/isPlainObject.ts 复核判定逻辑)、type 不能为 undefined 且必须是字符串——这也是官方建议「用字符串常量作为 action type」的底层原因。要 dispatch Promise、Observable、thunk 之类的值,则必须通过中间件(如 redux-thunk)扩展 dispatch,文档在 src/createStore.ts 的注释中明确了这一点。
3. reducer 执行期间禁止读取/订阅状态(src/createStore.ts):getState() 内部检查 isDispatching 标志,若在 reducer 执行期间调用会直接抛错「You may not call store.getState() while the reducer is executing」;subscribe() 与取消订阅在 dispatch 进行中同样被禁止(src/createStore.ts)。而订阅列表采用快照机制:每次 dispatch 前由 ensureCanMutateNextListeners() 浅拷贝一份监听器 Map(src/createStore.ts),确保 dispatch 过程中新增/移除的订阅不影响正在进行的这一次派发——这正是「订阅在每次 dispatch 前被快照」这一文档级承诺的实现方式。
4. reducer 替换与可观察性互操作。store 还提供 replaceReducer(src/createStore.ts),通过内部派发 ActionTypes.REPLACE 让新旧 reducer 都收到旧状态,实现状态树的无缝迁移——文档中 Code Splitting、热重载等进阶用法都依赖此 API;以及 Symbol.observable 的 observable() 方法(src/createStore.ts),使 store 可与 RxJS 等响应式库互操作。
5. createStore 的弃用立场与 legacy_createStore。在 src/createStore.ts 的 JSDoc 中,核心库明确标注 createStore 为 @deprecated,推荐改用 RTK 的 configureStore,并说明:如果你想在 TypeScript 编辑器中不显示弃用警告,可导入 legacy_createStore 并别名为 createStore 使用(import { legacy_createStore as createStore } from 'redux')。两者在 src/index.ts 中一同导出。换言之:
- 学习原理:用
createStore(legacy 写法),它「不会移除,但官方鼓励所有用户迁移到 RTK」; - 生产项目:用
configureStore,它内置了开发者模式校验、中间件组合等能力。
6. Redux 核心导出的全部 API。从 src/index.ts 可以确认,redux 核心包导出的函数仅有:createStore、legacy_createStore、combineReducers、bindActionCreators、applyMiddleware、compose、isAction、isPlainObject,以及供类型系统使用的 Store、Reducer、Action、Middleware 等类型。其中 compose 的实现见 src/compose.ts——从右到左组合单参数函数,是 store enhancer(如 applyMiddleware 内部)组合的基础构件。
五、学习路径与配套资源
文档为不同阶段的学习者规划了清晰的资源地图,均可在当前仓库内找到入口:
5.1 Redux Essentials 教程(自顶向下)
Redux Essentials 教程 是「top-down」教程,教你「以正确的方式使用 Redux」,采用最新推荐的 API 与最佳实践。官方建议从这里开始。共 8 部分:概述概念、应用结构、数据流、使用数据、异步逻辑、性能与数据归一化、RTK Query 基础与进阶(见 docs/tutorials/essentials 目录下各 part 文件)。
5.2 Redux Fundamentals 教程(自底向上)
Redux Fundamentals 教程 是「bottom-up」教程,从第一性原理出发、不引入任何抽象地讲解「Redux 如何工作」以及标准用法模式为何存在。共 8 部分,覆盖概念与数据流、状态/Action/Reducer、Store、UI 与 React、异步逻辑、标准模式与现代 Redux 写法。想理解本文第四节的源码行为,这条线是最优补充。
5.3 Learn Modern Redux 直播与示例
Redux 维护者 Mark Erikson 曾在 "Learn with Jason" 节目中讲解现代 Redux 用法,包含现场编码的示例应用,演示了如何用 Redux Toolkit、React-Redux hooks 与 TypeScript 组合,以及 RTK Query 数据获取 API(对应仓库文档 docs/tutorials/videos.md 中的资源介绍)。
5.4 仓库内置示例项目
Redux 仓库自带多个示例项目,演示 Redux 的各方面用法,几乎每个示例都有可在线交互的 CodeSandbox 版本。完整列表见 Examples 页面。结合仓库实际目录,各示例的侧重点如下:
- examples/counter:最基础的 RTK 计数器(本文 3.1 节代码的出处);
- examples/counter-ts:TypeScript 版计数器;
- examples/counter-vanilla:无 React 的纯 Redux 计数器(浏览器 ESM 直接引用);
- examples/shopping-cart:展示异步逻辑与组件测试的购物车;
- examples/todomvc、examples/todos、examples/todos-with-undo:TODO 应用,后者演示撤销/重做;
- examples/real-world、examples/async:GitHub 仓库浏览等「真实世界」数据流与异步场景;
- examples/tree-view、examples/universal:树形视图与服务端渲染场景。
5.5 其他文档资源
- Redux FAQ 回答了大量关于 Redux 用法的常见疑问;「Using Redux」文档区(docs/usage/index.md)涵盖派生数据、测试、reducer 逻辑组织与减少样板代码等主题;
- Redux 维护者的 "Practical Redux" 教程系列演示 React + Redux 的中级/高级实战技巧;
- 社区创建了数以千计的 Redux 相关库、插件与工具,官方推荐见 「Ecosystem」文档页。
六、获取帮助与讨论
- 官方答疑渠道是 Reactiflux Discord 社区的
#redux频道,适合所有学习与使用相关的问题; - 也可在 Stack Overflow 使用
redux标签提问; - 如有 bug 报告或反馈,请在 Redux 的 GitHub 仓库提交 issue。仓库内的 CONTRIBUTING.md 提供了贡献流程说明。
七、你应该使用 Redux 吗?
Redux 是组织状态的一个有价值工具,但你应当评估它是否适合你的具体场景。不要因为「别人说该用」就用 Redux——花时间理解使用它的潜在收益与代价。文档给出的建议是,以下情况适合使用 Redux:
- 你有相当数量随时间变化的数据;
- 你需要一个状态的单一数据源(single source of truth);
- 你发现把所有状态都放在顶层组件里已经不够用了。
关于「何时该用 Redux」的更深入讨论,可继续阅读仓库内的 FAQ:When should I use Redux? 与 Redux FAQ 总览。
八、小结
| 主题 | 关键结论 | 仓库内依据 |
|---|---|---|
| 官方推荐方案 | 新代码一律用 RTK(configureStore/createSlice),核心 createStore 已标记弃用 |
docs/introduction/GettingStarted.md、src/createStore.ts |
| 核心 API 面 | 核心包仅导出 createStore、combineReducers、bindActionCreators、applyMiddleware、compose 等 8 个函数 | src/index.ts |
| 数据流铁律 | dispatch 只接受 plain object 且 type 为非 undefined 字符串;reducer 执行期禁读/订阅 |
src/createStore.ts |
| 初始状态来源 | store 构造时派发 INIT action,由 reducer 默认参数提供初始状态 | src/createStore.ts |
| 无打包器用法 | ESM 构建可直接以 <script type="module"> 在浏览器运行 |
examples/counter-vanilla/index.html |
| 学习路线 | Essentials(自顶向下,推荐起点)+ Fundamentals(自底向上)+ 仓库 examples | docs/tutorials/essentials、examples/ |
完成本文的安装步骤、跑通 3.1/3.3 两个计数器示例、并按第五节路线读完 Essentials 教程后,你就同时掌握了 Redux 的「正确用法」与「底层原理」,可以开始构建自己的 Redux 应用了。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
