MobX 有什么用?相比 React Hooks 的优点?

93 阅读8分钟

一、MobX 的底层原理(简版)

MobX 的核心是通过 依赖追踪发布-订阅模式 实现的响应式系统:

  1. 可观察状态(Observable) 将数据转换为可观察对象。

  2. 依赖收集(Tracking) 在组件渲染或计算过程中,自动记录依赖的可观察属性。

  3. 触发更新(Reaction) 当依赖的状态变化时,自动触发相关组件或计算逻辑的更新。

这种机制比基于手动依赖声明的 useMemouseEffect 更精准和高效。

二、MobX 与 React 函数式组件深度整合

1. mobx vs mobx-react-lite

  • mobx:MobX 核心库,提供状态管理的基础能力(observable, action, computed, reactionautorun,toJS, runInActionmakeAutoObservable等)。

  • mobx-react-lite:专为 React 函数式组件设计的轻量级绑定库,提供 observer, useObserver, useLocalStore(已废弃)等 API。特点

    • 仅支持函数组件(类组件需用 mobx-react)。

    • 更小的体积,更好的性能优化。


2. 核心 API 在函数式组件中的应用

(1) observer (组件响应式绑定)

将组件包裹为“观察者”,自动追踪依赖的可观察状态,并在状态变化时重新渲染组件。

import { observer } from "mobx-react-lite";
import { store } from "./store";

const UserList = observer(() => {
  return (
    <div>
      {store.users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
});

(2) useLocalStore(已废弃 → 改用 useMemo + makeAutoObservable

旧版用于在组件内创建局部可观察状态的 Hook(现推荐直接使用 useMemo)。

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

const Counter = observer(() => {
  // 旧版写法(已废弃)
  const store = useLocalStore(() => ({
    count: 0,
    increment() {
      store.count++;
    },
  }));

  // 推荐新写法
  const store = useMemo(() => makeAutoObservable({
    count: 0,
    increment() {
      this.count++;
    },
  }), []);

  return <button onClick={store.increment}>{store.count}</button>;
});
makeAutoObservable

makeAutoObservablemobx 核心库提供的一个 API,用于快速将一个对象转换为可观察的(Observable)状态容器。它的作用是自动推断对象中的属性类型:

  • 普通属性 → 转换为 observable
  • 方法 → 转换为 action
  • Getter 函数 → 转换为 computed
import { makeAutoObservable } from "mobx";

class CounterStore {
  count = 0; // 自动变为 observable

  constructor() {
    makeAutoObservable(this); // 自动处理所有属性和方法
  }

  increment() { // 自动变为 action
    this.count++;
  }

  get double() { // 自动变为 computed
    return this.count * 2;
  }
}

(3) useObserver (局部响应式区域)

在组件内部标记一个需要响应式更新的区域,替代整个组件用 observer 包裹。

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

const UserProfile = () => {
  const { user } = store;

  return useObserver(() => (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  ));
};

(4) Observer (组件内局部观察)

类似 useObserver,但以组件形式包裹需要响应式更新的部分。

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

const UserProfile = () => {
  return (
    <div>
      <Observer>
        {() => (
          <span>{store.user.name}</span>
        )}
      </Observer>
    </div>
  );
};

(5) autorunreaction (副作用管理)

在组件中处理副作用(如日志、网络请求),需在 useEffect 中管理生命周期。

import { autorun, reaction } from "mobx";
import { useEffect } from "react";

const UserTracker = () => {
  useEffect(() => {
    // 自动追踪依赖,当 store.user.id 变化时触发
    const disposer = autorun(() => {
      console.log("User ID changed:", store.user.id);
    });

    // 手动定义依赖,并处理新旧值
    const reactionDisposer = reaction(
      () => store.user.age,
      (age, prevAge) => {
        console.log(`Age changed from ${prevAge} to ${age}`);
      }
    );

    return () => {
      disposer();
      reactionDisposer();
    };
  }, []);

  return null;
};

(6) toJS (转换为普通对象)

将可观察对象转换为普通 JavaScript 对象(常用于传递给外部库或持久化)。

import { toJS } from "mobx";

const DataExporter = observer(() => {
  const handleExport = () => {
    const plainData = toJS(store.data); // 去除 observability
    sendToAPI(plainData);
  };

  return <button onClick={handleExport}>Export Data</button>;
});

(7) actioncomputed (状态管理)

在 Store 类或对象中定义状态修改方法和派生值。

import { makeAutoObservable, action, computed } from "mobx";

class UserStore {
  users = [];
  filter = "";

  constructor() {
    makeAutoObservable(this);
  }

  // Action
  setFilter = action((filter) => {
    this.filter = filter;
  });

  // Computed
  get filteredUsers() {
    return this.users.filter(user => 
      user.name.includes(this.filter)
    );
  }
}

(8) configure (全局配置)

设置 MobX 的全局行为(如严格模式、装饰器兼容性)。

import { configure } from "mobx";

// 强制所有状态修改必须在 action 中
configure({ enforceActions: "observed" });

// 启用装饰器语法支持(如果项目使用装饰器)
configure({ useProxies: "always" });

(9) useAsObservableSource(已废弃 → 改用 useMemo

将外部 props 转换为可观察对象(旧版 API,现推荐其他方式)。

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

const UserProfile = observer((props) => {
  const observableProps = useAsObservableSource(props);

  return <div>{observableProps.user.name}</div>;
});

3. 异步操作处理

在函数式组件中,使用 runInAction 确保异步后的状态修改被追踪。

import { runInAction } from "mobx";

class PostStore {
  posts = [];
  loading = false;

  fetchPosts = async () => {
    this.loading = true;
    try {
      const data = await fetchPostsAPI();
      runInAction(() => {
        this.posts = data;
        this.loading = false;
      });
    } catch (error) {
      runInAction(() => {
        this.loading = false;
      });
    }
  };
}

三、MobX 与 React Hooks

1. 简单场景: useState 足够

如果只是管理 组件内部的简单状态(如一个计数器),useState 完全够用:

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

此时引入 MobX 反而是过度设计。

React Hooks 的适用场景:

  1. 简单组件状态(如表单输入、按钮状态)。

  2. 小型项目或原型开发(快速迭代,无需长期维护)。

  3. 无需复杂状态共享(组件间状态通过 Props 传递即可)。


2. 复杂场景:MobX 的碾压性优势

何时该选择 MobX?

  1. 应用中存在大量跨组件共享状态(如用户信息、主题、全局配置)。

  2. 需要频繁处理派生状态或复杂计算(如仪表盘、实时数据过滤)。

  3. 追求极致的渲染性能(避免不必要的子组件更新)。

  4. 团队熟悉响应式编程概念(降低维护成本)。

如果项目中出现以下信号,就该考虑 MobX 了

  • 你开始频繁使用 useMemomemo 来优化性能。

  • 组件树中多层级传递状态导致代码难以维护。

  • 需要处理大量派生状态或异步副作用。

当遇到以下场景时,MobX 的优势会立刻显现:

(1) 跨组件状态共享

需求: 两个组件(ComponentAComponentB)共享同一个计数器状态,点击按钮时同步更新。

  • 使用 useState + Context

    痛点

    • 必须使用 memo 包裹子组件,否则任意状态变化都会导致所有子组件重新渲染。
    • 当 Context 中的状态复杂时,拆分多个 Context 或优化依赖会非常繁琐。
    • // 使用 Context 共享状态
      import React, { useState, createContext, useContext, memo } from "react";
      
      // 定义 Context
      const CounterContext = createContext<{
        count: number;
        increment: () => void;
      }>(null!);
      
      // Provider 组件
      const CounterProvider = ({ children }: { children: React.ReactNode }) => {
        const [count, setCount] = useState(0);
        const increment = () => setCount((c) => c + 1);
      
        return (
          <CounterContext.Provider value={{ count, increment }}>
            {children}
          </CounterContext.Provider>
        );
      };
      
      // 子组件 A(需用 memo 避免无效渲染)
      const ComponentA = memo(() => {
        const { increment } = useContext(CounterContext);
        return <button onClick={increment}>+1</button>;
      });
      
      // 子组件 B(需用 memo 避免无效渲染)
      const ComponentB = memo(() => {
        const { count } = useContext(CounterContext);
        return <span>Count: {count}</span>;
      });
      
      // 使用组件
      const App = () => {
        return (
          <CounterProvider>
            <ComponentA />
            <ComponentB />
          </CounterProvider>
        );
      };
      
  • 使用 MobX直接通过 observer 包裹组件

    • 组件自动追踪依赖,无需手动优化渲染,精准更新。
    • 状态修改直接(无需 setState),逻辑更集中。
import { observer } from "mobx-react-lite";
import { makeAutoObservable } from "mobx";
import { useMemo } from "react";

// 使用 useMemo + makeAutoObservable 创建 Store
const createCounterStore = () => {
  return makeAutoObservable({
    count: 0,
    increment() {
      this.count++;
    },
  });
};

// 组件 A
const ComponentA = observer(() => {
  const store = useMemo(createCounterStore, []); // 实际项目中应通过 Context 共享 Store
  return <button onClick={store.increment}>+1</button>;
});

// 组件 B
const ComponentB = observer(() => {
  const store = useMemo(createCounterStore, []); // 实际项目中应通过 Context 共享 Store
  return <span>Count: {store.count}</span>;
});

// 使用组件(实际项目应通过 Provider 共享 Store)
const App = () => {
  return (
    <>
      <ComponentA />
      <ComponentB />
    </>
  );
};

(2) 派生状态(Computed Values)

需求: 根据用户选择的过滤条件(全部、已完成、未完成),动态显示待办事项列表。

  • 使用 useMemo

    • 必须手动声明 useMemo 的依赖项(todosfilter),若遗漏会导致数据不一致。
    • 当派生逻辑复杂时,代码可读性下降。
import { useState, useMemo } from "react";

const TodoListHooks = () => {
  const [todos, setTodos] = useState<{ text: string; done: boolean }[]>([]);
  const [filter, setFilter] = useState<"all" | "done" | "undone">("all");

  // 手动管理派生状态
  const filteredTodos = useMemo(() => {
    return todos.filter((todo) => {
      if (filter === "all") return true;
      return filter === "done" ? todo.done : !todo.done;
    });
  }, [todos, filter]); // 必须显式声明依赖

  return (
    <div>
      <select value={filter} onChange={(e) => setFilter(e.target.value as any)}>
        <option value="all">All</option>
        <option value="done">Done</option>
        <option value="undone">Undone</option>
      </select>
      <ul>
        {filteredTodos.map((todo, index) => (
          <li key={index}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
};
  • 使用 MobX 的 computed

    • 派生状态通过 getter 自动追踪依赖,无需手动声明。
    • 代码更贴近业务逻辑,可读性更高。
import { observer } from "mobx-react-lite";
import { makeAutoObservable } from "mobx";
import { useMemo } from "react";

// 使用 useMemo + makeAutoObservable 创建 Store
const createTodoStore = () => {
  return makeAutoObservable({
    todos: [] as { text: string; done: boolean }[],
    filter: "all" as "all" | "done" | "undone",
    get filteredTodos() { // 自动追踪依赖
      return this.todos.filter((todo) => {
        if (this.filter === "all") return true;
        return this.filter === "done" ? todo.done : !todo.done;
      });
    },
    setFilter(filter: "all" | "done" | "undone") {
      this.filter = filter;
    },
  });
};

const TodoListMobx = observer(() => {
  const store = useMemo(createTodoStore, []);

  return (
    <div>
      <select
        value={store.filter}
        onChange={(e) => store.setFilter(e.target.value as any)}
      >
        <option value="all">All</option>
        <option value="done">Done</option>
        <option value="undone">Undone</option>
      </select>
      <ul>
        {store.filteredTodos.map((todo, index) => (
          <li key={index}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
});

(3) 异步操作与副作用

需求: 从 API 获取用户数据,处理加载状态和错误。

  • 使用 useEffect

    • 需要手动管理 loadingerror 状态。
    • 异步逻辑分散在组件中,难以复用。
import { useState, useEffect } from "react";

const UserListHooks = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  useEffect(() => {
    const fetchUsers = async () => {
      setLoading(true);
      try {
        const response = await fetch("/api/users");
        const data = await response.json();
        setUsers(data);
        setError("");
      } catch (err) {
        setError("Failed to fetch users");
      } finally {
        setLoading(false);
      }
    };
    fetchUsers();
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>{error}</div>;
  return <div>{users.map((user) => user.name)}</div>;
};
  • 使用 MobX 的 autorun/reaction自动追踪依赖,声明式处理副作用。

    • 异步逻辑封装在 Store 中,可复用且易于测试。
    • 使用 runInAction 确保状态修改被追踪,代码更安全。
import { observer } from "mobx-react-lite";
import { makeAutoObservable, runInAction } from "mobx";
import { useMemo } from "react";

// 使用 useMemo + makeAutoObservable 创建 Store
const createUserStore = () => {
  return makeAutoObservable({
    users: [] as any[],
    loading: false,
    error: "",
    async fetchUsers() {
      this.loading = true;
      try {
        const response = await fetch("/api/users");
        const data = await response.json();
        runInAction(() => {
          this.users = data;
          this.error = "";
        });
      } catch (err) {
        runInAction(() => {
          this.error = "Failed to fetch users";
        });
      } finally {
        runInAction(() => {
          this.loading = false;
        });
      }
    },
  });
};

const UserListMobx = observer(() => {
  const store = useMemo(createUserStore, []);
  useEffect(() => {
    store.fetchUsers();
  }, [store]);

  if (store.loading) return <div>Loading...</div>;
  if (store.error) return <div>{store.error}</div>;
  return <div>{store.users.map((user) => user.name)}</div>;
});

(4) 性能优化

  • 使用 useState 父组件状态变化会导致所有子组件重新渲染,需手动用 memo 或拆分组件。

  • 使用 MobXobserver 组件仅在依赖的状态变化时更新,粒度更细,性能更高


3. MobX 的核心价值总结

场景React Hooks (useState + Context)MobX
简单组件状态✅ 简单直接❌ 过度设计
跨组件状态共享⚠️ 需手动优化,易导致性能问题✅ 自动精准更新
派生状态⚠️ 依赖 useMemo,需手动管理依赖✅ 自动追踪,声明式
异步/副作用⚠️ 需 useEffect 和清理逻辑✅ 声明式,自动依赖追踪
性能优化⚠️ 依赖 memo 和拆分组件✅ 细粒度更新,零成本优化
代码复杂度⚠️ 复杂逻辑时代码臃肿✅ 逻辑集中,高可维护性

四、最佳实践与常见问题

1. 状态组织

  • 单一 Store vs 多 Store根据项目复杂度拆分 Store(如 UserStore, PostStore, UIStore)。

  • Context API 共享 Store使用 React Context 全局共享 Store。

import { createContext, useContext } from "react";
import { UserStore } from "./stores";

const StoreContext = createContext<UserStore>(null!);

const App = () => {
  const userStore = new UserStore();
  return (
    <StoreContext.Provider value={userStore}>
      <ChildComponent />
    </StoreContext.Provider>
  );
};

const ChildComponent = () => {
  const userStore = useContext(StoreContext);
  return <div>{userStore.username}</div>;
};

2. 性能优化

  • 避免不必要的渲染: 使用 observerObserver 精细控制渲染范围。

  • 细粒度拆分组件: 将大组件拆分为多个观察者组件,减少重渲染范围。


3. 常见陷阱

  • 直接修改状态: 确保在 action 中修改状态(严格模式下会报错)。
  • 未清理副作用: autorunreaction 需在 useEffect 的清理函数中销毁。
  • 过度使用:toJS 仅在必要时转换,避免破坏响应式。