Skip to main content

MobX 状态管理

MobX 是一个基于响应式编程(Reactive Programming)的状态管理库。它的核心哲学是:任何源自应用状态的东西都应该自动地获得。相比于 Redux 的函数式和不可变(Immutable)约束,MobX 更倾向于面向对象和可变状态(Mutable),学习成本更低,样板代码更少。

在 React 中的使用

在 React 中使用 MobX,通常结合 mobx(核心逻辑)和 mobx-react-lite(React 绑定层)来实现。

1. 定义 Store

使用 makeAutoObservable 可以非常方便地将类的属性转化为响应式状态,将方法转化为 Action。

// store/counterStore.js
import { makeAutoObservable } from "mobx";

class CounterStore {
count = 0;

constructor() {
// 自动将 count 转为 observable,increment 转为 action,doubleCount 转为 computed
makeAutoObservable(this);
}

// Action:修改状态的方法
increment() {
this.count++;
}

decrement() {
this.count--;
}

// Computed:衍生状态,基于已有的 observable 计算得出,具有缓存特性
get doubleCount() {
return this.count * 2;
}
}

// 导出单例
export const counterStore = new CounterStore();

2. 在 React 组件中接入

使用 mobx-react-lite 提供的 observer 高阶组件包裹 React 组件。它会自动追踪组件渲染期间使用到的 Observable 属性,并在属性改变时触发重渲染。

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

// 使用 observer 包裹组件
const CounterComponent = observer(() => {
// 注意:只有这里实际读取了 counterStore.count,组件才会依赖 count 的变化
return (
<div style={{ padding: "20px", border: "1px solid #ccc" }}>
<h3>MobX Counter</h3>
<p>Count: {counterStore.count}</p>
<p>Double Count: {counterStore.doubleCount}</p>
<button onClick={() => counterStore.increment()}>增加</button>
<button onClick={() => counterStore.decrement()}>减少</button>
</div>
);
});

export default CounterComponent;

底层原理剖析

面试官问:“能讲讲 MobX 的响应式原理吗?它是怎么做到精准更新 React 组件的?”

1. 数据劫持与依赖收集(Proxy + TFRP 机制)

MobX 的核心是数据劫持与透明函数响应式编程 (TFRP)。MobX 6+ 全面拥抱 ES6 Proxy,完美拦截对象的读写操作。

为了方便面试时向面试官白板推演,我们可以用一段极简伪代码来展示它的底层工作流:

// 全局指针:指向当前正在执行的副作用(如 React 组件的 Render 函数)
let currentObserver = null;

function observable(target) {
const deps = new Map(); // 依赖收集器:记录 { 属性名 -> [观察者1, 观察者2...] }

return new Proxy(target, {
get(obj, key) {
// 【1. 依赖收集】:如果当前有组件正在渲染,把它加入到该属性的订阅列表中
if (currentObserver) {
let subscribers = deps.get(key);
if (!subscribers) {
subscribers = new Set();
deps.set(key, subscribers);
}
subscribers.add(currentObserver);
}
return obj[key];
},
set(obj, key, value) {
obj[key] = value;
// 【2. 派发更新】:属性被修改时,找出所有订阅了该属性的观察者,依次触发它们
const subscribers = deps.get(key);
if (subscribers) {
subscribers.forEach((observer) => observer()); // 比如触发组件的 forceUpdate
}
return true;
},
});
}

2. 与 React 的结合原理(observer 做了什么?)

observer HOC 本质上是把你的 React 组件包裹进了一个跟踪环境(Reaction)中。

// observer 极简底层伪代码
function observer(Component) {
return function WrappedComponent(props) {
// 利用 useReducer 制造一个强制更新的钩子
const [, forceUpdate] = useReducer((c) => c + 1, 0);

// 每次组件渲染时,挂载 currentObserver
currentObserver = forceUpdate;

// 执行真正的组件代码,此时触发内部 observable 对象的 Proxy get 拦截
// forceUpdate 就会被无声无息地塞进 deps 里!
const result = Component(props);

// 渲染完毕,清空指针
currentObserver = null;

return result;
};
}
  • 极限细粒度更新:结合上面的伪代码可以看出,依赖收集是在组件运行 render 时动态发生的。如果一个 Store 有 100 个属性,你的组件 Component(props) 执行时只读取了 store.a,那么只有 store.aget 拦截器会收集到 forceUpdate。修改 store.b 毫无影响。这就是为什么 MobX 性能极高且不需要手写依赖数组(如 Redux 的 useSelector)的原因。

3. Computed 值的缓存机制

Computed 属性是 MobX 性能优化的利器。 它不仅是一个观察者(监听底层的 Observable),本身也是一个 Observable(可被组件监听)。 只有当它依赖的底层值发生改变,并且当前有组件正在消费这个 Computed 值时,它才会重新计算。否则,直接返回上一次的缓存值,避免复杂的重复运算。

面试话术提炼(优缺点对比)

  • 优点:上手极快(面向对象,符合直觉),样板代码极少(直接修改属性即可),默认性能极佳(运行时依赖收集带来的极致细粒度更新)。
  • 缺点:状态修改过于自由(Mutable),如果不严格规范 Action,很容易导致状态变更来源难以追踪(缺乏像 Redux 那样清晰的 Time Travel 调试);在超大型、极其复杂的团队协作项目中,可维护性和状态流转的可预测性不如 Redux。