Redux¶
写在之前¶
纯函数与副作用¶
在学习 React 和 Redux 过程中,常常看到 纯函数 (Pure Function) 与 副作用 (Side Effect),理解这两个概念可以更好的理解官方文档和他们代码的实现逻辑。
纯函数必须满足两个条件:
- 输出只取决于输入参数, 同样的输入永远得到同样的输出;
- 没有副作用(No Side Effects),不改变函数外部任何状态
例如:
满足第 1 个条件(参数 a,b 确定,返回值确定),但不满足第 2 个条件(输出了 log,改变了函数外面(side)的状态;
不是纯函数示例:
-
依赖外部变量,不可预测
-
改变全局变量,产生副作用
-
修改参数
-
产生随机性
-
打印日志、访问IO、网络请求
Note
redux reducer 一定要是纯函数, 因为 state 的更新需要:
- 可预测;
- 可回滚;
- 可重放;
- 可比较;
- 可记录每步状态历史
Redux DevTools 能"回放时间线",就是因为 reducer 是纯函数:
- 给定相同的旧 state 和相同的 action, reducer 永远返回同样的 nextState
副作用(Side Effects)
任何在函数之外,对系统状态产生影响的行为,都叫副作用。
副作用的本质
函数不仅仅返回一段值,而是对外界造成某种影响。所有影响"外部世界"的行为,都算副作用。
常见副作用行为列表:
- 改变外部变量(包括参数);
- 修改传入对象(数组、对象 mutable);
- 打印日志;
- 网络请求 / IO;
- 存取 LocalStorage / Cookie;
- DOM 操作;
- 访问系统时间 / 随机数;
- 数据库读写 / 文件操作 / Socket;
Note
Redux 官方明确:
- reducer 禁止产生任何副作用
- 函数只能做一件事:计算新的 state
React 与 Redux¶
-
React State 是局部状态
React 的哲学:状态只属于产生它的 UI 区域。
React 的设计目标:组件驱动 UI, State 是和组件绑定的:- 挂在组件实例上;
- 生命周期由组件决定;
- 组件销毁, state 消失;
- 这是一种“局部封装”
React 只负责渲染层:
- React 的工作是把 state 映射到 DOM;
- 不关心业务逻辑、全局数据、缓存、跨页面状态;
- 所以它的状态模型是:组件 → 局部 → 生命周期绑定 → 瞬时状态
Note
React State 的边界 == 组件边界。 它不是应用状态,它是 UI 层状态。
-
Redux State 是全局状态
Redux 不是 UI 框架,它是应用状态容器。它解决的问题:
- 多页面共享数据
- 跨组件状态同步
- 消息/异步请求状态
- 持久化 / Debug / 时光回溯
Redux 核心理念来自 状态机 + 事件溯源:
(prevState, action) => nextStateNote
在 Redux 开来,组件应该是状态消费者,不是状态拥有者
初识状态管理¶
- 所谓"状态"就是数据在某一时刻的值;
- "状态变化"就是数据从一个值变成另一个值;
- 状态管理就是记录这些变化,使它们变得可控、可预测、可调试、可追溯,并能驱动界面渲染。
- 为了实现这些目标,我们不能直接修改原始数据,而是先复制一份,在复制后的数据上进行修改。这个不修改原数据,而修改副本的做法,称为不可变性(immutability), 参考可变更新与不可变更新;
- Redux 就是一种实现这种状态管理的工具,它提供了一套方法来管理、记录和控制状态变化。
Note
Redux 的核心理念: Redux 把状态放到一个统一的地方,用一套严格规则管理变化.
Redux通过一个全局 Store 管理状态,并约束状态如何更新;- 状态只允许通过 action 去变更;
- 变更逻辑由 用户定义的 reducer 函数 决定
Redux 的三个基本工具¶
-
Store 状态仓库
- 存放所有共享数据(即:状态)
- 存放改变数据的成员方法(即:成员函数)
- 间接存放自定义的 reducer 函数(说间接,是因为自定义的数据处理函数被成员函数
dispatch()调用来实现数据变化)
-
Action 意图描述
- 告诉 reducer 要做什么
- 不更新状态
- 只是一个说明对象
{ type: "...", payload: ... }
-
Reducer 纯逻辑
- reducer 函数是用户自定义的函数,其中处理的 action 与 2 中用户自定义的 action 对应;
- 根据 action 把数据"复制" 一份,并在新的数据上修改数据,即:完成了状态更新;
- 纯函数:对输入做处理、输出新状态(即:输出一个新的数据,不仅数值是更新后的,而是整个数据是新的一份),不修改原状态
使用 Redux 的一个最小例子¶
Redux 的最小闭环:
- Store 存状态;
- Action 描述意图
- 自定义的 Reducer 函数
counterReducer()处理变化
counter.js 代码:
运行结果:
必要的解释:
- const 保护变量的绑定
- 展开运算符 (Spread Operator, ...)
store是一个拥有 5 个成员方法的对象;const store = createStore(counterReducer)把自定义的 reducer 函数counterReducer传递给store供其成员方法dispatch()调用:dispatch()用来触发 Action
用 JS 写一个原生的类状态管理¶
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | |
运行结果:
箭头函数的简写形式
等价于
1. 单个参数 --> 可省略括号2. 函数体只有一行表达式 → 可省略 {} 和 return
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | |
运行结果:
Redux¶
Redux reducer¶
Note
reduce 在这里不是"减少"的意思,请参考下面 Merriam-Webster 的部分意思:
- to draw together or cause to converge: CONSOLIDATE
// reduce all the questions to one - to bring to a specified state or condition
// the impact of the movie reduced them to tears - to change the denominations or form of without changing the value
- to add one or more electrons to (an atom or ion or molecule)
Redux reducer 名称借用了数组的 array.reduce() 方法的名称,因为状态是"持续累积"的:
- UI 用户操作
- 网络请求返回
- 定时任务触发
- WebSocket 推送
这些都是"事件序列",一个一个发生,驱动 state 演变。
Redux 并没有像 array.reduce() 一样"把数组 reduce 一遍",它借用了 reduce 最核心的抽象:把一个序列逐步累积为一个最终值。区别只是:
Array.reduce的序列是数组元素Redux.reduce的序列是 Action
Note
状态不是被完整覆盖,而是不断更新和累积。
Redux reducer 必须是:
- 纯函数
- 不可变更新
- 无副作用
Redux 的四层次架构¶
| 层 | 责任 | 作用 | 是否官方强制 |
|---|---|---|---|
| store | 状态容器 | 集成 reducer/middleware | 是 |
| slices | 状态域模型 | 定义每块状态与变更规则 | ✔ 推荐(RTK) |
| selectors | 状态访问 API | 只读抽象 + 性能 | ✖ 但强烈提倡 |
| hooks | UI 适配入口 | 统一 dispatch/select 类型 | ✖ 业界共识 |
-
Store 层:管理状态生命周期 (系统集成器)
管理状态系统 —— 根节点与配置中心
-
Slice 层:描述状态与业务变更(状态域模型)
定义状态空间 —— 状态是什么 + 怎样变化
-
Selector 层:抽象数据访问(状态访问 API)
读状态的唯一方式 —— 稳定、高性能、无副作用
-
Hook 层:提供 UI 入口(状态访问适配器(React 入口))
- UI 访问状态的接口 —— 弱耦合、类型安全、业务方便
-
React-Redux 官方提供的“React 接口”, React-Redux 提供:
- useDispatch()
- useSelector()
Note
Redux 官方“强制”的只有两件事:
- Store
- Reducer / Slice(Redux Toolkit)
selectors、hooks 的拆分是社区最佳实践,不是强制标准