MobX 状态管理:响应式与可观察对象

FreeGuideOnline 最新 2026-06-15

什么是 MobX

MobX 是一个简单、可扩展的状态管理库,通过透明地应用函数式响应式编程让状态管理变得直观。它遵循“一切派生自应用状态,并且自动同步”的理念,核心概念是自动追踪状态的变化并高效地更新所有依赖。

相比 Redux 这类单向数据流库,MobX 的写法更贴近面向对象思维,适合中小型团队快速开发,也能在大型项目中通过合理的分层保持可维护性。本教程聚焦于最核心的机制:响应式与可观察对象。

核心概念

1. Observable State(可观察状态)

MobX 中的数据需要被标记为“可观察”,这样 MobX 才能追踪它的读写。你可以把普通的对象、数组、类属性变成可观察的。

import { makeObservable, observable, action } from "mobx";

class TodoStore {
  todos = [];

  constructor() {
    makeObservable(this, {
      todos: observable,
      addTodo: action
    });
  }

  addTodo(text) {
    this.todos.push({ text, completed: false });
  }
}

上面的例子中,todos 数组被 observable 标记后,任何读取它的 computedreaction 都会自动订阅它的变化。

2. Computed Values(计算值)

计算值是从可观察状态中派生出的值,当依赖的状态变化时自动重新计算。它们会被缓存,只有依赖项改变才会重新求值。

import { computed } from "mobx";

class TodoStore {
  // ...前面的定义

  get completedCount() {
    return this.todos.filter(t => t.completed).length;
  }

  constructor() {
    makeObservable(this, {
      completedCount: computed
    });
  }
}

computed 像一个高效的数据管道,你可以在组件或其他 reaction 中像访问普通属性一样使用它,MobX 会保证它总是最新的。

3. Actions(动作)

动作是任何改变状态的函数。使用 action 标记可以带来性能优化和更好的调试体验。在严格模式下,只有 action 内才能修改 observable。

addTodo(text) {
  this.todos.push({ text, completed: false });
}

toggleTodo(index) {
  this.todos[index].completed = !this.todos[index].completed;
}

4. Reactions(反应)

反应是当状态变化时自动运行的副作用。常见的反应有 autorunreactionwhen,以及 React 组件中的 observer

import { autorun } from "mobx";

const store = new TodoStore();
autorun(() => {
  console.log(`剩余未完成:${store.todos.length - store.completedCount}`);
});

每当 todoscompletedCount 变化,autorun 中的回调就会执行。

可观察对象的原理

MobX 通过 ES6 Proxy 或 Object.defineProperty 拦截对可观察对象的读写。当你在 computedreaction 中读取一个 observable 属性时,MobX 会将当前的派生函数注册为该属性的观察者。写入属性时,它会通知所有观察者重新计算或执行。

关键点:

  • 自动跟踪:你无需手动订阅,只需在跟踪上下文中读取即可。
  • 细粒度更新:只精确到被修改的属性,而不是整个对象重新渲染。
  • 可观察数组与 Map:MobX 专门实现了可观察数组和 Map 结构,行为与原生的几乎一致,但具有响应式能力。

在 React 中使用 MobX

使用 mobx-react-liteobserver 函数包裹组件,使其自动追踪内部使用的 observable 并重新渲染。

import { observer } from "mobx-react-lite";

const TodoList = observer(({ store }) => (
  <ul>
    {store.todos.map((todo, i) => (
      <li key={i} onClick={() => store.toggleTodo(i)}>
        {todo.text} {todo.completed ? "✓" : ""}
      </li>
    ))}
  </ul>
));

observer 将组件转换为“反应式组件”,任何在渲染期间读取的 observable 变化都会导致组件重新渲染,但只会针对实际改变的依赖重新渲染,性能优异。

动手构建一个完整的 Todo 示例

下面是一个完整的、可直接运行的 Todo 应用,使用 Vite + React + MobX。

1. 目录结构

src/
  store/
    TodoStore.js
  App.jsx
  main.jsx

2. 定义 Store

// store/TodoStore.js
import { makeObservable, observable, computed, action } from "mobx";

class TodoStore {
  todos = [];

  constructor() {
    makeObservable(this, {
      todos: observable,
      completedCount: computed,
      addTodo: action,
      toggleTodo: action,
      removeTodo: action
    });
  }

  get completedCount() {
    return this.todos.filter(t => t.completed).length;
  }

  addTodo(text) {
    this.todos.push({ text, completed: false, id: Date.now() });
  }

  toggleTodo(id) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) todo.completed = !todo.completed;
  }

  removeTodo(id) {
    this.todos = this.todos.filter(t => t.id !== id);
  }
}

export default new TodoStore(); // 单例导出

3. 组件与页面

// App.jsx
import { useState } from "react";
import { observer } from "mobx-react-lite";
import todoStore from "./store/TodoStore";

const App = observer(() => {
  const [text, setText] = useState("");

  const handleAdd = () => {
    if (text.trim()) {
      todoStore.addTodo(text);
      setText("");
    }
  };

  return (
    <div style={{ padding: 20 }}>
      <h2>MobX Todo ({todoStore.todos.length - todoStore.completedCount} 剩余)</h2>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
        onKeyDown={e => e.key === "Enter" && handleAdd()}
      />
      <button onClick={handleAdd}>添加</button>
      <ul>
        {todoStore.todos.map(todo => (
          <li key={todo.id}>
            <span
              onClick={() => todoStore.toggleTodo(todo.id)}
              style={{
                textDecoration: todo.completed ? "line-through" : "none",
                cursor: "pointer"
              }}
            >
              {todo.text}
            </span>
            <button onClick={() => todoStore.removeTodo(todo.id)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
});

export default App;

4. 入口文件

// main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")).render(<App />);

处理异步操作

异步操作同样建议放在 action 中,可以使用 runInAction 或者直接将回调标记为 action。

import { runInAction } from "mobx";

async fetchTodos() {
  const response = await fetch("/api/todos");
  const data = await response.json();
  runInAction(() => {
    this.todos = data;
  });
}

或者使用 flow 生成器函数(适用于旧版装饰器写法),但现代推荐 async/await + runInAction

MobX 的调试与最佳实践

  • 启用严格模式configure({ enforceActions: "observed" }),强制只能通过 action 修改状态,让变更可预测。
  • 使用 computed 而非衍生状态:避免在组件内做复杂的计算,交给 computed 保证缓存和一致性。
  • 保持状态扁平化:虽然 MobX 支持深层嵌套的可观察对象,但过深的嵌套会让调试和序列化复杂化,尽量把数据设计为扁平的集合。
  • 配合 DevTools:安装 MobX DevTools 浏览器扩展,可以实时查看依赖树、observe 值和 action 调用栈。

常见问题

Q:MobX 和 Redux 如何选择?
A:MobX 的写法更少样板代码,适合快速迭代和面向对象的团队;Redux 强制单向数据流和纯函数,更适合需要严格约束的大型协作项目。两者可以共存,根据模块复杂度选择。

Q:observable 对象可以像普通对象一样使用吗?
A:大部分情况下是的,但要注意不要直接解构使用(会丢失响应性),如需解构可以配合 toJS() 或使用 observer 包裹组件。

Q:如何在不使用类的情况下使用 MobX?
A:可以使用 makeAutoObservable 或直接 observable 函数包装普通对象,创建函数式的 store。

const counter = makeAutoObservable({
  count: 0,
  increment() { this.count++; }
});

总结

MobX 通过“可观察状态、计算值、动作和反应”这四个核心理念,让状态管理变得自然而高效。你只需要定义数据、声明如何修改、描述如何计算,剩下的自动同步交给 MobX。对于希望减少模板代码、同时保持高性能响应式更新的开发者,它是一个强有力的工具。立即在下一个项目中尝试 MobX,体验“状态管理本该如此”的感受。