前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版

MobX 核心概念解析与 React 协同最佳实践,以及 2026 年的替代选择

首页2024-11-26 12:40:12Front-End
ReactMobX状态管理前端工程化Redux

接手过一个用 Redux 写的后台系统,一个订单详情页要改三个字段,我得在 actionTypes.js、actions/order.js、reducers/order.js 三个文件之间来回跳,写完还要回组件里补 mapStateToProps。四十多行样板代码,换来的实际逻辑就是 state.detail = payload。那阵子我特别理解为什么会有人转向 MobX。

这篇把 MobX 的核心 API 逐个拆一遍,配一个能跑的计数器和一套 Store 组织方式。文章原本是 2024 年写的,这次补了一节 2026 年的现状:MobX 在 React 18 并发渲染和 React Server Components 下会遇到什么,以及什么情况下我会建议换成别的方案。老代码怎么写的我照原样留着,新的判断另起一节说,两边不混。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • MobX 想解决 Redux 的哪几个具体痛点,以及它付出了什么代价
  • observable、computed、action 三块基石的职责边界
  • observer 是怎么让组件跟着数据自动重渲的
  • autorun、reaction、flow 各自的适用场景和常见误用
  • 一个完整可跑的计数器示例,把上面所有概念串起来
  • Store 组织、严格模式配置、异步处理的工程化做法
  • React 18 并发渲染和 RSC 之下 MobX 的现状,以及 2026 年的替代选择

# 一、MobX 想解决什么

# 1.1 Redux 的三个痛点

在 MobX 出现之前,React 项目的状态管理默认就是 Redux。它采用单一数据源,所有状态存在一个只读的 store 里,只能通过 action 和 reducer 更新。逻辑清晰,但用久了有三个地方磨人。

第一个是状态冗余更新。Redux 的状态是不可变的,每次更新都要创建新的状态对象,结果是与 store 连接的 UI 组件都会重新渲染,即使它们只依赖状态里的一小块数据。你可以用 PureRenderMixin 或者 reselect 做缓存优化,但这是额外的开发成本,而且优化对不对得靠自己盯。

第二个是样板代码。写 action types、action creators、reducers,一个再简单的功能也得把这套模板走一遍。

第三个是学习曲线。要理解 Redux 的数据流,得先搞明白 action、reducer、middleware 这几层怎么串,对新人不太友好。

# 1.2 MobX 的三块基石

MobX 走的是完全不同的路子。它用 JavaScript 的代理(Proxy)机制自动追踪状态的读写,谁读了哪个字段它记着,那个字段变了就通知谁。这套模式叫响应式编程。

拆开看是三件事。可观察的状态(Observable State),把普通 JS 对象转成可观察对象,任何修改都能被追踪。自动推导(Computed Values),根据现有状态算出衍生值,和 Excel 的公式一个意思,改了 A1 单元格,依赖它的公式自动重算。响应式副作用(Reactions),状态变化时自动执行副作用,比如更新 UI、打日志、发请求。

第三件里最重要的那个 reaction,就是 React 组件本身。

# 1.3 和 Redux 摆在一起看

特性 MobX Redux
数据结构 多 store,支持普通对象 单一 store,不可变数据
更新方式 直接修改,可选严格模式 只能通过 action 和 reducer
依赖追踪 自动追踪,按需更新 手动订阅,可能过度渲染
代码量 简洁,样板代码少 较多模板代码
学习成本 较低 较高
调试工具 友好的时间旅行调试 强大的 DevTools

从数据流的覆盖范围看,Redux 管的是 STORE -> VIEW -> ACTION 一整圈,MobX 更关注 STORE -> VIEW 这一段。action 在 MobX 里是可选的,你完全可以直接改状态。方便归方便,代价是状态的修改入口不统一,出了问题不好回溯改的是谁。

# 1.4 优点和缺点

优点有三条。

第一是基于运行时的数据订阅。MobX 的数据依赖始终保持最小,而且是运行时自动追踪的。用 Redux 的时候你可能一不小心就多订阅或者少订阅了数据,多了性能差,少了不更新,都得靠 PureRenderMixin 和 reselect 给 selector 做缓存来补。

第二是可以用面向对象的方式组织领域模型。能抽出清晰 domain model 的场景下,OOP 组织起来是真的顺手。MobX 支持用引用的方式引用数据,很容易形成模型图(model graph),一个订单能直接拿到它的用户对象,而不是拿一个 userId 再去别的 slice 里查。

第三是改数据自然。MobX 基于原生的 JS 对象、数组和 Class 实现,改数据没有额外语法成本,不用每次都返回一个新对象,直接操作就行。

缺点也有三条。

第一是最佳实践和社区积累相对少。MobX 出现得晚,你遇到的问题可能社区还没人遇到过,而且它没有很好的扩展和插件机制。

第二是随意修改 store 的风险。Redux 里唯一能改数据的地方是 reducer,这保证了应用的可预测性;MobX 可以在任何地方改数据触发更新,用起来会有点不踏实。这个问题后来是有解的,MobX 从 2.2 版本开始加入了 action 支持,开启 strict mode 之后只有 action 能改数据,修改入口就收敛回来了。

第三是逻辑层的限制。如果更新逻辑没法很好地封装进 domain class,用 Redux 会更合适。另外 MobX 缺少类似 redux-saga 那样的库,复杂的业务流程编排不知道放哪合适。

# 二、核心 API 逐个拆

# 2.1 observable

observable 负责把普通属性变成可观察属性。被标记的属性暴露给观察者,值一变,所有依赖它的地方都会收到通知。可观察的值可以是基本类型、引用类型、普通对象、类实例、数组和 Map。

现在推荐的入口是 makeAutoObservable,在构造函数里调一次,它会自动推断每个成员该是什么:

import { makeAutoObservable } from 'mobx'

class Counter {
  // 基本类型
  count = 0
  name = '计数器'

  // 引用类型
  user = {
    name: '张三',
    age: 25
  }

  // 数组
  items = []

  constructor() {
    // makeAutoObservable 会自动推断类型并添加相应的装饰器
    makeAutoObservable(this)
  }

  increment() {
    this.count++
  }

  decrement() {
    this.count--
  }

  setName(name) {
    this.name = name
  }
}

const counter = new Counter()
@前端进阶之旅: 代码已经复制到剪贴板

推断规则很直白,普通属性变成 observable,getter 变成 computed,普通方法变成 action,generator 方法变成 flow。

老项目里更常见的是装饰器写法,需要配 babel 才能用:

// 使用装饰器写法
import { observable, computed, action } from 'mobx'

class Counter {
  @observable count = 0
  @observable title = 'this is about page'
  @observable num = 0

  // computed - 计算值
  @computed get getUserInfo() {
    return `我是computed经过计算的getter, current num: ${this.num}`
  }

  // action - 动作
  @action.bound add() {
    this.num++
  }

  @action.bound reduce() {
    this.num--
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

这套写法在 MobX 4/5 时代是主流,现在仍然支持,但从 MobX 6 开始它不再是默认路径。原因是装饰器提案长期没定稿,MobX 6 把 makeObservable / makeAutoObservable 提为主入口,装饰器降级成可选语法糖,而且用装饰器时仍然要在构造函数里调一次 makeObservable(this)。新写的代码建议直接用 makeAutoObservable,能省掉整套 babel 装饰器配置。

@action.bound 里的 .bound 是把方法绑到实例上,这样你可以直接 onClick={counter.add} 而不用 () => counter.add()。makeAutoObservable 默认不做这个绑定,需要的话得显式写 { add: action.bound }。

# 2.2 computed

计算值是根据现有状态或其它计算值衍生出来的值。它自带缓存,基础值没变的话,重复读取直接返回缓存,不会引起虚拟 DOM 的重新渲染。

import { makeAutoObservable, computed } from 'mobx'

class OrderStore {
  items = []
  taxRate = 0.1

  constructor() {
    makeAutoObservable(this, {
      // 显式声明 computed 属性
      subtotal: computed,
      tax: computed,
      total: computed
    })
  }

  get subtotal() {
    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
  }

  get tax() {
    return this.subtotal * this.taxRate
  }

  get total() {
    return this.subtotal + this.tax
  }

  addItem(item) {
    this.items.push(item)
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

注意 total 依赖 tax,tax 依赖 subtotal,MobX 会自己把这条依赖链排好,items 一变,三个值按顺序失效重算。这条链你不用手写任何订阅代码。

有个坑要注意。computed 的缓存只在它被某个 reaction 观察时才生效,如果你在组件外面直接读一个没人观察的 computed,MobX 会每次都重新计算。这在写单元测试或者在事件回调里直接读 store 时容易踩到,表现是「明明加了 computed 却没变快」。

computed 还支持 setter 做逆向推导:

class Foo {
  @observable length = 2

  @computed get squared() {
    return this.length * this.length
  }

  set squared(value) {
    // 这是一个自动的动作,不需要注解
    // 设置 squared 时,自动推导出 length
    this.length = Math.sqrt(value)
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

这个特性用得不多,但在做「双向绑定的换算单元」时挺好使,比如摄氏度和华氏度两个输入框互相推导。

# 2.3 action

只有在 action 里才能修改 state。这条规则默认是不强制的,要靠严格模式打开:

fe
  • 一、MobX 想解决什么
    • 1.1 Redux 的三个痛点
    • 1.2 MobX 的三块基石
    • 1.3 和 Redux 摆在一起看
    • 1.4 优点和缺点
  • 二、核心 API 逐个拆
    • 2.1 observable
    • 2.2 computed
    • 2.3 action
    • 2.4 observer
    • 2.5 autorun
    • 2.6 reaction
    • 2.7 flow
  • 三、计数器完整示例
  • 四、工程化落地的几条实践
    • 4.1 Store 的组织方式
    • 4.2 严格模式配置
    • 4.3 组件中的使用方式
    • 4.4 异步操作
    • 4.5 性能相关
  • 五、2026 年再看 MobX
    • 5.1 MobX 6 之后的写法变化
    • 5.2 React 18 并发渲染下的适配
    • 5.3 RSC 和 App Router 是真的不合
    • 5.4 现在会选什么
    • 5.5 什么情况下 MobX 仍然合适
  • 总结
  • 参考

← React 18 并发机制深度解析,Fiber 可中断渲染与时间切片原理大文件上传实战,Node.js 分片上传断点续传与秒传完整落地 →