Skip to content

🔥状态传递:如何在无 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,
正是这种传递的优雅抽象