我越来越觉得:状态管理工具,别只在 Redux 和 Zustand 里选了

3 阅读6分钟

Redux 是老牌方案,Zustand 是现在更火的轻量方案,但 AI 项目前端经常还需要第三种思路。

最近连续做了几个 AI 相关前端:聊天界面、Agent 工作台、工具调用面板、知识库检索页、模型配置页。做到后面我越来越强烈地意识到一件事:

AI 项目前端最难的,往往不是调用模型 API,而是管理那些“不断变化、彼此关联、还带副作用”的状态。

比如你以为自己只是在做一个聊天页,结果实际要管理的是:

  • 消息列表
  • 流式输出中的中间态
  • 当前是否在调用工具
  • 工具调用参数和结果
  • 会话上下文
  • 用户操作历史
  • 多个面板之间的状态同步
  • 错误重试和 loading

这时候如果你还在 Redux 和 Zustand 之间来回摇摆,通常会遇到两种不同但都很真实的问题:

  • 用 Redux:

  • 状态定义越来越大

  • action / reducer / selector 一层层铺开

  • AI 容易补出一堆“能跑但样板很多”的代码

  • 用 Zustand:

  • 起步很轻,写 demo 特别快

  • 但一旦业务变重,容易把 action、异步、副作用、派生逻辑全塞回 store 或 hooks

  • AI 也容易顺着这个趋势继续“越写越散”

我后来干脆把这些项目都切到了 easy-model 这种 class-based 的思路,体验差别挺明显。

AI 项目前端,为什么特别容易把状态管理写崩

传统中后台页面很多时候是“表单 + 列表 + 弹窗”。

AI 项目前端不一样,它的状态天然更像一个“运行中的系统”:

  • 用户发出输入
  • 模型开始流式返回
  • 中途触发 tool call
  • tool call 完成后继续生成
  • 页面多个区域要同时更新
  • 还要能插入日志、埋点、调试、回放

这类状态有两个特点:

  1. 业务过程很重
  2. 组件之间共享状态的需求很多

如果状态只是几个布尔值,Redux、Zustand、easy-model 其实都能做。

但一旦你要表达“一个 AI 会话正在发生什么”,你会发现最自然的抽象其实不是一堆独立的 hook,而是一个有字段、有方法、有行为边界的对象。

也就是这种形式:

class AIChatModel {
  messages: Message[] = [];
  streamingText = '';
  isStreaming = false;
  toolCalls: ToolCall[] = [];
  activeTool: ToolCall | null = null;

  @loader.load()
  async sendMessage(content: string) {
    this.messages.push({ role: 'user', content });
    this.isStreaming = true;

    const stream = await llm.stream(this.messages);

    for await (const chunk of stream) {
      this.streamingText += chunk;
    }

    this.messages.push({
      role: 'assistant',
      content: this.streamingText,
    });

    this.streamingText = '';
    this.isStreaming = false;
  }
}

我不是说这段代码比 Zustand “高级”。

恰恰相反,它最大的优点是:足够直觉。

字段就是状态,方法就是业务动作。你不需要在脑子里来回切 Redux 那套 action / reducer / selector,也不需要在 Zustand 里反复决定“这段逻辑到底塞 store、hook 还是 component”。

这也是 easy-model 对 AI 编程工具友好的地方

这两个月我最大的体感是,AI 对不同状态方案的“续写稳定性”差别还挺大的:

AI coding assistant 不是不能写前端,而是它特别怕“概念层级太碎”。

比如:

  • Redux 对 AI 来说不是不能写,而是容易写得太模板化

  • Zustand 对 AI 来说不是不能写,而是容易随着业务增长变得过于随手

  • easy-model 这种组织方式,对 AI 来说就比较顺:

  • 一个类就能描述一个完整业务域

  • 类名天然就是语义边界

  • 方法名天然就是业务意图

  • 字段和 UI 展示有直接映射关系

  • 同一个模型可以被多个组件共享

你给 Cursor、Copilot、Claude Code、Codeium 这类工具一个 prompt:

“帮我做一个 AI 聊天页,支持会话、流式消息、工具调用状态、错误重试和全局 loading”

如果底层状态层是碎片化的,AI 往往会越写越散。

但如果你先给它一个 ChatModelToolPanelModelSessionModel 这种结构,生成质量会明显更稳定。

不是 AI 变聪明了,而是你的代码组织方式让它更容易理解了。

easy-model 适合 AI 前端的点,不只是“代码少”

很多人一看 class-based 状态管理,第一反应是:这是不是只是 Redux / Zustand 之外的又一种写法?

easy-model 真正让我觉得顺手的,不只是 class,而是它把几个 AI 场景里高频的诉求放在了一起:

1. 同参数共享实例

这点很适合 AI 会话、标签页、工作区这类场景。

const chat = useModel(ChatModel, ['session-1']);

你在消息区、状态栏、工具面板里都传同样的参数,就能拿到同一份实例。

这比自己手搓一层 context + cache 要省事很多。

2. 深度 watch

AI 界面里经常要做日志、埋点、调试面板、状态变化追踪。

watch / useWatcher 这类能力很适合接这些副作用逻辑,不用把追踪逻辑塞进组件里到处飞。

3. 统一 loading

AI 项目里异步方法特别多:

  • 发消息
  • 拉历史记录
  • 调工具
  • 跑检索
  • 保存配置

如果每个地方都自己维护 loading = true/false,最后经常会乱。

loader 这种统一管理方式,在复杂交互里会省很多心智负担。

4. IoC / inject

这个点不一定所有人都马上用上,但如果你在做:

  • 多模型供应商适配
  • 多环境接口切换
  • 插件式工具注入
  • Agent 工具注册

依赖注入会比把依赖到处硬编码舒服很多。

为什么我觉得这类库更适合“AI 参与开发”的时代

过去我们选状态库,更多看的是:

  • 社区规模
  • 学习成本
  • 性能
  • 生态

但现在我觉得应该再加一个维度:

它适不适合被 AI 生成、理解、修改、续写。

这个维度以前不存在,现在越来越重要。

因为很多项目不是“人手写完再让 AI 看看”,而是:

  • 先让 AI 起草 70%
  • 人来补边界和判断
  • 再让 AI 继续扩展

这时最值钱的,不只是 API 优雅,而是:

代码结构要让 AI 不容易迷路。

easy-model 给我的感觉就是,它很适合拿来当这类项目的“中间层表达方式”:

  • 对人类开发者来说足够直觉
  • 对 AI 来说也足够容易模式化学习

如果你现在就在做这些东西,可以试试

我会优先推荐 easy-model 给这几类项目:

  • AI 聊天/Agent 前端
  • 多面板协作型工作台
  • 配置、检索、执行混合型页面
  • 想用 AI 快速搭 UI 和业务骨架的 React 项目
  • 不想再写太多样板代码,但又不想失去类型和组织性的团队

当然,它不一定适合所有项目。

如果你的页面极其简单,Zustand 依然是非常好的选择。

如果你的团队已经深度绑定 Redux 工具链,也没必要为了“换库”硬换。

但如果你已经开始出现下面这些信号:

  • 一个页面里有好几个相互关联的状态域
  • 组件越来越多,共享逻辑越来越乱
  • AI 能生成代码,但你接手后越看越散

那 easy-model 值得你认真试一下。

最后

我现在对 easy-model 的定位很直接:

它不是为了取代 Redux 或 Zustand 而存在,而是提供一种更适合 AI 时代 React 业务建模的写法。

尤其是 AI 项目前端,这种感觉会更明显。

项目地址:

如果你也在做 AI 前端,或者正在找一种对 AI coding assistant 更友好的 React 状态组织方式,欢迎去看看。

如果这篇对你有启发,也欢迎顺手点个 Star。