Redux 基础教程,第一部分:Redux 概述与核心概念
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
- Redux 是什么以及为什么需要使用它
- Redux 的核心术语与概念
- 数据在 Redux 应用中的流动方式
简介
欢迎来到 Redux 基础教程!本教程将向你介绍 Redux,并通过我们最新推荐的工具和最佳实践,教你如何正确使用它。完成学习后,你将能够运用所学工具和模式构建自己的 Redux 应用。
在本教程的第一部分,我们将涵盖使用 Redux 需要了解的核心概念和术语;在第二部分:Redux 应用结构中,我们将分析典型的 React + Redux 应用,了解各个模块如何协同工作。
从第三部分:Redux 基础数据流开始,我们将运用这些知识构建一个具备真实功能的小型社交媒体动态应用,观察这些模块在实际中的运作方式,并讨论使用 Redux 的重要模式和准则。
如何阅读本教程
本教程重点展示_如何_正确使用 Redux,并在过程中解释相关概念,帮助你理解如何正确构建 Redux 应用。
我们尽量确保解释对初学者友好,但仍需预设你已掌握以下知识:
- 熟悉 HTML & CSS
- 熟悉 ES2015 语法特性
- 了解 React 术语:JSX、函数组件、Props、State 和 Hooks
- 掌握异步 JavaScript 及发起 HTTP 请求
- 基本了解 TypeScript 语法与应用
若你对这些主题尚未熟悉,建议先花时间掌握它们,再回来学习 Redux。我们随时在此等候!
请确保已在浏览器中安装 React 和 Redux 的开发者工具扩展:
-
React DevTools 扩展:
-
Redux DevTools 扩展:
什么是 Redux?
首先需要理解 "Redux" 究竟是什么。它能做什么?解决哪些问题?为何需要使用它?
Redux 是一种用于管理和更新全局应用状态的模式和库,其机制是:UI 触发称为 "actions" 的事件来描述发生了什么,而称为 "reducers" 的独立更新逻辑则响应这些事件来更新状态。 它作为集中存储库,管理需要在整个应用中共享的状态,并通过规则确保状态只能以可预测的方式更新。
为何应该使用 Redux?
Redux 帮助你管理 "全局" 状态——即应用中多个部分需要共享的状态。
Redux 提供的模式和工具让你更轻松地理解应用状态在何时、何地、为何及如何被更新,以及状态变更时应用逻辑将如何响应。Redux 引导你编写可预测且可测试的代码,从而确保应用按预期运行。
何时应该使用 Redux?
Redux 能协助管理共享状态,但如同任何工具,它存在权衡取舍。需要学习更多概念,编写更多代码。它还会增加代码的间接性,并要求遵循特定约束。这是在短期生产力与长期生产力之间的权衡。
Redux 在以下场景更为适用:
-
应用中有大量状态需要在多个地方使用
-
应用状态随时间频繁更新
-
状态更新逻辑可能较为复杂
-
应用代码库规模中等或庞大,且可能由多人协作开发
并非所有应用都需要 Redux。请仔细评估您构建的应用类型,确定哪些工具最适合解决当前问题。
若不确定 Redux 是否适合您的应用,以下资源提供更多指导:
Redux 库与工具
Redux 核心是一个独立的小型 JS 库,通常与以下包配合使用:
Redux Toolkit
Redux Toolkit 是我们推荐的 Redux 逻辑编写方案。它包含构建 Redux 应用必需的工具包和函数,内置最佳实践建议,能简化多数 Redux 任务,避免常见错误,并提升开发效率。
React-Redux
Redux 可与任何 UI 框架集成,最常与 React 搭配使用。React-Redux 是我们的官方包,让 React 组件能通过读取状态片段和派发 action 来与 Redux store 交互。
Redux DevTools 扩展
Redux DevTools 扩展 可展示 Redux store 中状态随时间的变化历史。这使您能高效调试应用,包括使用强大的"时间旅行调试"等技术。
Redux 术语与概念
在编写实际代码前,我们先了解使用 Redux 需要掌握的核心术语与概念。
状态管理
首先观察一个简单的 React 计数器组件。它在组件状态中跟踪数字,并在按钮点击时递增:
function Counter() {
// State: a counter value
const [counter, setCounter] = useState(0)
// Action: code that causes an update to the state when something happens
const increment = () => {
setCounter(prevCounter => prevCounter + 1)
}
// View: the UI definition
return (
<div>
Value: {counter} <button onClick={increment}>Increment</button>
</div>
)
}
这是一个包含以下部分的自包含应用:
-
状态(state):驱动应用的真实数据源
-
视图(view):基于当前状态的 UI 声明式描述
-
动作(actions):用户输入触发的事件,引发状态更新
这体现了**"单向数据流"**的简单示例:
-
状态描述应用在特定时间点的状况
-
UI 基于该状态渲染
-
当事件发生时(如用户点击按钮),根据事件内容更新状态
-
UI 基于新状态重新渲染

然而,当有多个组件需要共享并使用同一状态时,尤其是当这些组件位于应用的不同部分时,这种简单性可能会被打破。有时可以通过状态提升到父组件来解决,但这并非总是有效。
一种解决方案是将共享状态从组件中提取出来,置于组件树之外的中心化位置。这样,我们的组件树就变成了一个大型"视图",任何组件无论位于树中何处,都能访问状态或触发动作!
通过定义并分离状态管理相关的概念,并强制执行保持视图与状态独立性的规则,我们让代码获得了更好的结构和可维护性。
这就是 Redux 的核心思想:一个中心化的位置存储应用的全局状态,以及更新状态时需要遵循的特定模式,使代码行为可预测。
不可变性
"可变"(Mutable)意味着"可更改"。如果某物是"不可变"(Immutable)的,则永远无法被修改。
JavaScript 对象和数组默认都是可变的。创建对象后可以修改其字段内容,创建数组后同样可以更改其元素:
const obj = { a: 1, b: 2 }
// still the same object outside, but the contents have changed
obj.b = 3
const arr = ['a', 'b']
// In the same way, we can change the contents of this array
arr.push('c')
arr[1] = 'd'
这称为对象或数组的"突变"(mutating)。内存中的对象或数组引用未变,但其内部内容已被更改。
要以不可变方式更新值,代码必须创建现有对象/数组的副本,然后修改这些副本。
我们可以使用 JavaScript 的数组/对象展开运算符,以及返回新数组副本而非修改原数组的数组方法来实现:
const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3
},
b: 2
}
const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42
}
}
const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')
// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
React 和 Redux 要求所有状态更新都以不可变方式完成。稍后我们将探讨这在何时何地为何重要,以及更简便的不可变更新逻辑编写方式。
关于 JavaScript 中不可变性工作原理的更多信息,请参阅:
核心术语
在继续之前,您需要熟悉以下重要 Redux 术语:
动作(Actions)
动作是包含 type 字段的普通 JavaScript 对象。可将动作视为描述应用中发生事件的对象。
type 字段应为描述性字符串(如 "todos/todoAdded")。我们通常按 "domain/eventName" 格式命名,第一部分表示动作所属功能类别,第二部分描述具体事件。
动作对象可包含其他事件相关信息字段。按约定,这类信息应放在名为 payload 的字段中。
典型动作对象示例如下:
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
动作创建器(Action Creators)
动作创建器是创建并返回动作对象的函数。通常用于避免手动编写动作对象:
const addTodo = text => {
return {
type: 'todos/todoAdded',
payload: text
}
}
归约器(Reducers)
归约器是接收当前 state 和 action 对象的函数,决定如何更新状态(如有必要)并返回新状态:(state, action) => newState。可将归约器视为基于接收动作(事件)类型处理事件的事件监听器。
"归约器"函数得名于其类似 Array.reduce() 方法的回调函数。
Reducer 必须始终遵循一些特定规则:
-
它们应该只根据
state和action参数来计算新的状态值 -
不允许直接修改现有的
state。相反,必须通过复制现有state并对副本进行修改,实现 不可变更新。 -
必须是"纯函数"——不能包含异步逻辑、生成随机值或引发其他"副作用"
稍后我们将详细讨论 reducer 的规则,包括其重要性和正确遵循方法
Reducer 函数内部的逻辑通常遵循相同的步骤序列:
-
检查该 reducer 是否关心此 action
- 如果是,则复制当前状态,用新值更新副本后返回
-
否则直接返回现有状态不作更改
以下是一个小型 reducer 示例,展示了每个 reducer 应遵循的步骤:
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/increment') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1
}
}
// otherwise return the existing state unchanged
return state
}
Reducer 内部可以使用任何逻辑决定新状态:if/else、switch、循环等
Detailed Explanation: Why Are They Called 'Reducers?'
The Array.reduce() method lets you take an array of values, process each item in the array one at a time, and return a single final result. You can think of it as "reducing the array down to one value".
Array.reduce() takes a callback function as an argument, which will be called one time for each item in the array. It takes two arguments:
previousResult, the value that your callback returned last timecurrentItem, the current item in the array
The first time that the callback runs, there isn't a previousResult available, so we need to also pass in an initial value that will be used as the first previousResult.
If we wanted to add together an array of numbers to find out what the total is, we could write a reduce callback that looks like this:
const numbers = [2, 5, 8]
const addNumbers = (previousResult, currentItem) => {
console.log({ previousResult, currentItem })
return previousResult + currentItem
}
const initialValue = 0
const total = numbers.reduce(addNumbers, initialValue)
// {previousResult: 0, currentItem: 2}
// {previousResult: 2, currentItem: 5}
// {previousResult: 7, currentItem: 8}
console.log(total)
// 15
Notice that this addNumbers "reduce callback" function doesn't need to keep track of anything itself. It takes the previousResult and currentItem arguments, does something with them, and returns a new result value.
A Redux reducer function is exactly the same idea as this "reduce callback" function! It takes a "previous result" (the state), and the "current item" (the action object), decides a new state value based on those arguments, and returns that new state.
If we were to create an array of Redux actions, call reduce(), and pass in a reducer function, we'd get a final result the same way:
const actions = [
{ type: 'counter/increment' },
{ type: 'counter/increment' },
{ type: 'counter/increment' }
]
const initialState = { value: 0 }
const finalResult = actions.reduce(counterReducer, initialState)
console.log(finalResult)
// {value: 3}
We can say that Redux reducers reduce a set of actions (over time) into a single state. The difference is that with Array.reduce() it happens all at once, and with Redux, it happens over the lifetime of your running app.
Store
当前 Redux 应用状态保存在称为 store 的对象中
通过传入 reducer 创建 store,其提供 getState 方法获取当前状态值:
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}
Dispatch
Redux store 提供 dispatch 方法。更新状态的唯一方式是调用 store.dispatch() 并传入 action 对象。store 将执行 reducer 函数并保存新状态值,之后可通过 getState() 获取更新后的值:
store.dispatch({ type: 'counter/increment' })
console.log(store.getState())
// {value: 1}
可将 dispatch action 视为在应用中"触发事件"。当事件发生后,我们需要通知 store。Reducer 如同事件监听器,当收到关注的 action 时,会响应更新状态
通常我们通过调用 action creator 来 dispatch 对应 action:
const increment = () => {
return {
type: 'counter/increment'
}
}
store.dispatch(increment())
console.log(store.getState())
// {value: 2}
Selector
Selector 是知道如何从 store 状态值提取特定信息的函数。随着应用规模增大,这能避免不同模块读取相同数据时重复逻辑:
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2
Redux 应用数据流
此前我们提到的"单向数据流"描述了应用更新的步骤序列:
-
状态描述应用在特定时间点的状况
-
UI 基于该状态渲染
-
当事件发生时(如用户点击按钮),根据事件内容更新状态
-
UI 基于新状态重新渲染
对于 Redux,我们可以将这些步骤细化为:
-
初始化阶段:
- 使用根 reducer 函数创建 Redux store
- store 执行根 reducer 并将返回值保存为初始
state - 首次渲染 UI 时,组件访问 Redux store 当前状态决定渲染内容,同时订阅后续 store 更新以感知状态变化
-
更新过程:
- 应用中发生事件(例如用户点击按钮)
- 应用代码向 Redux store 分发 action(如
dispatch({type: 'counter/increment'})) - store 使用先前的
state和当前action重新运行 reducer 函数,并将返回值保存为新state - store 通知所有已订阅的 UI 部分 store 已更新
- 每个需要 store 数据的 UI 组件检查所需状态部分是否变更
- 数据变更的组件使用新数据强制重新渲染,从而更新界面显示
以下是该数据流的可视化展示:

学习要点
Redux 确实包含许多需要记忆的新术语和概念。以下是本节涵盖的核心内容:
- Redux 是管理全局应用状态的库
- 通常配合 React-Redux 实现 Redux 与 React 的集成
- Redux Toolkit 是编写 Redux 逻辑的标准方式
- Redux 的更新模式分离“事件描述”与“状态变更”
- Action 是包含
type字段的普通对象,描述应用中“发生了什么” - Reducer 是根据先前状态和 action 计算新状态的函数
- 当 action 被 分发 时,Redux store 会运行根 reducer
- Action 是包含
- Redux 采用“单向数据流”架构
- 状态描述应用在特定时刻的状况,UI 基于该状态渲染
- 事件触发时的处理流程:
- UI 分发 action
- store 运行 reducers 并根据事件更新状态
- store 通知 UI 状态变更
- UI 根据新状态重新渲染
下一步是什么?
我们已经了解 Redux 应用的各个独立模块。接下来请继续学习 第二部分:Redux Toolkit 应用结构,通过完整示例探索这些模块如何协同工作。