🔥状态传递:如何在无 this 的情况下管理状态?
——使用 State Monad 模拟“可变状态”的纯函数表达
“状态不必可变,
只需清晰传递。”
一、问题:没有 this,如何管理状态?
在纯函数式编程中:
- 没有
this.state - 没有
let x = ...; x = newValue; - 没有共享可变状态
但现实需要状态:计数器、缓存、配置、随机数生成器…
解决方案:把状态作为参数传递,并通过 State Monad 封装模式。
二、基础:状态即输入与输出
最简单的状态传递
js
// 纯函数:输入状态,输出新状态 + 结果
const increment = (state) => [state + 1, state + 1];
// 使用
let [result, newState] = increment(0); // [1, 1]
[result, newState] = increment(newState); // [2, 2]- 纯函数
- 无副作用
- 模板代码多,难组合
三、抽象:State Monad 封装“状态上下文”
State Monad 定义
一个函数:S → (A, S)
即:接受旧状态 S,返回结果值 A 和新状态 S
js
// State Monad 构造
const State = run => ({
run, // S → (A, S)
// map: 转换结果,不改变状态流转
map: f => State(s => {
const [a, s1] = run(s);
return [f(a), s1];
}),
// chain: 序列化状态操作
chain: f => State(s => {
const [a, s1] = run(s);
return f(a).run(s1); // f(a) 返回新的 State
})
});四、构建状态操作函数
1. get:读取当前状态
js
const get = State(s => [s, s]);2. put:设置新状态
js
const put = newState => State(s => [undefined, newState]);3. modify:更新状态
js
const modify = fn => State(s => [undefined, fn(s)]);4. evalState:执行并返回结果
js
const evalState = (state, initialState) => state.run(initialState)[0];5. execState:执行并返回最终状态
js
const execState = (state, initialState) => state.run(initialState)[1];五、真实示例:计数器
用 State Monad 实现
js
// 定义操作
const increment = modify(s => s + 1);
const decrement = modify(s => s - 1);
const reset = put(0);
// 组合操作
const program = State.of(undefined)
.chain(_ => increment) // → 1
.chain(_ => increment) // → 2
.chain(_ => decrement) // → 1
.chain(_ => reset); // → 0
// 执行
execState(program, 0); // 0六、复杂示例:带日志的配置管理
js
// 状态:{ config: Object, log: String[] }
const initialState = {
config: { theme: 'light' },
log: []
};
// 操作:更新配置并记录日志
const updateConfig = (key, value) =>
State(s => {
const newConfig = { ...s.config, [key]: value };
const newLog = [...s.log, `Set ${key}=${value}`];
return [undefined, { config: newConfig, log: newLog }];
});
// 读取配置
const getConfig = key =>
State(s => [s.config[key], s]);
// 组合程序
const configProgram = pipe(
() => updateConfig('theme', 'dark'),
chain(() => updateConfig('language', 'zh')),
chain(() => getConfig('theme'))
)();
// 执行
const [result, finalState] = configProgram.run(initialState);
// result: 'dark'
// finalState.log: ['Set theme=dark', 'Set language=zh']- 纯函数
- 状态变更可追溯
- 易于测试和重放
七、对比:State Monad vs 命令式
| 方式 | 优点 | 缺点 |
|---|---|---|
命令式 (this.state) | 直观,性能好 | 难测试,副作用,不可预测 |
| State Monad | 纯函数,可组合,可重放 | 模板代码多,学习成本高 |
React useState | 简单,集成好 | 仅限组件,非通用 |
| Redux | 集中管理,可调试 | 模板多,需中间件处理异步 |
State Monad 是“通用的、纯函数的状态机”。
八、工程实践:何时使用?
推荐场景
- 复杂的状态转换逻辑(如游戏引擎)
- 需要重放或时间旅行的系统
- 高可靠性系统(金融、航天)
- 函数式库内部实现
不推荐场景
- 简单组件状态(用
useState) - 性能敏感的循环(闭包开销)
- 团队不熟悉 FP
九、现代替代方案
1. immer + 纯函数
js
import produce from 'immer';
const reducer = (state, action) =>
produce(state, draft => {
draft.count++;
});写起来像命令式,结果是不可变的。
2. Zustand / Jotai
轻量级状态库,支持函数式思维。
3. fp-ts State Monad
生产级实现,类型安全。
结语:状态不必“可变”,只需“可传递”
State Monad 的真谛不是模拟 this,
而是将状态变更变成可组合的函数。
你不再问: “怎么改这个变量?”
而是问: “这个操作如何转换状态?”
这是一种思维范式的转变:
从“命令机器做事”,
到“描述数据如何流动”。
在无 this 的世界里,
状态不是“拥有”的,
而是“传递”的。
而 State Monad,
正是这种传递的优雅抽象。