在 React 项目里做状态管理,很多人一开始会想到 Redux,但 MobX 凭借它的“响应式”机制,让状态更新变得像操作普通 JavaScript 对象一样自然。今天我们就来聊聊 MobX 里的核心装饰器 observer,看看它怎么帮我们把 React 组件和 MobX 的响应式 store 绑定到一起。不管你是老手还是刚接触,看完这篇你就能在类组件和函数组件之间自由切换使用。
一、为什么要用 MobX 的 observer
React 本身是通过 setState 或 hooks 来触发重新渲染的,但 MobX 的 store 是独立的状态容器。如果没有 observer,组件不会自动响应 store 里数据的变化。observer 就像一个“监听器”,它包裹住组件后,组件就会订阅它用到的所有 MobX 可观察数据。只要那些数据变了,组件就会自动重新渲染,完全不用你手动去调用 forceUpdate 或者监听 subscribe。
简单说:你定义了一个 @observable count,在组件里用了 store.count,当 count 改变时,组件自动更新。这就是 observer 干的事。
二、在类组件中使用 observer
2.1 基本用法
类组件是 React 的传统写法。要把 MobX 集成进来,需要用 observer 函数包裹整个类。如果你用的是装饰器语法(需要配置 Babel 或 TypeScript 支持),可以在类上面加 @observer 装饰器;如果不喜欢装饰器,直接调用 observer(MyComponent) 效果一样。
注意:类组件里访问 store 属性时,MobX 会自动追踪依赖。你只需要保证 store 是可观察对象(通过 observable 或 makeObservable 定义即可)。
2.2 示例:计数器
下面是一个完整的计数器示例,包含一个简单的 MobX store 和类组件,技术栈是 React + MobX(使用 mobx-react 包)。
// 技术栈:React + MobX(mobx-react)
import React, { Component } from 'react';
import { observable, action, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
// 定义 MobX store
class CounterStore {
count = 0;
constructor() {
// 必须调用 makeObservable 来标记可观察属性和动作
makeObservable(this, {
count: observable, // 标记 count 为可观察
increment: action.bound, // 标记 increment 为动作,并绑定 this
decrement: action.bound,
});
}
increment() {
this.count += 1;
}
decrement() {
this.count -= 1;
}
}
// 创建 store 实例
const store = new CounterStore();
// 使用 @observer 装饰器包裹组件
@observer
class CounterClassComponent extends Component {
render() {
return (
<div style={{ padding: 20 }}>
<h2>类组件计数器</h2>
<p>当前计数:{store.count}</p>
<button onClick={store.increment}>+1</button>
<button onClick={store.decrement}>-1</button>
</div>
);
}
}
export default CounterClassComponent;
解释一下:makeObservable 是 MobX 6+ 推荐的写法,它告诉 MobX 哪些属性是可观察的,哪些方法是动作。动作里修改可观察属性,组件会自动重渲染。@observer 让组件变成了响应式组件。
三、在函数组件中使用 observer
3.1 基本用法
函数组件现在更流行,官方也推荐用 hooks。对应 mobx-react-lite 包里的 observer 函数,用法和类组件类似:直接包裹函数组件即可。注意:函数组件里可以用 useLocalObservable 或直接引用外部的 store。因为函数组件没有实例,所以 MobX 的依赖追踪依然生效,只要组件渲染时访问了可观察属性,就会自动订阅。
3.2 示例:计数器
同样的计数器,我们换成函数组件实现。技术栈依然是 React + MobX,但这次用 mobx-react-lite(轻量版,只支持函数组件)。
// 技术栈:React + MobX(mobx-react-lite)
import React from 'react';
import { observable, action, makeObservable } from 'mobx';
import { observer } from 'mobx-react-lite';
// 定义 store,和类组件一样
class CounterStore {
count = 0;
constructor() {
makeObservable(this, {
count: observable,
increment: action.bound,
decrement: action.bound,
});
}
increment() {
this.count += 1;
}
decrement() {
this.count -= 1;
}
}
// 创建实例(通常可以放在全局或通过 context 提供)
const store = new CounterStore();
// 函数组件用 observer 包裹
const CounterFuncComponent = observer(() => {
return (
<div style={{ padding: 20 }}>
<h2>函数组件计数器</h2>
<p>当前计数:{store.count}</p>
<button onClick={store.increment}>+1</button>
<button onClick={store.decrement}>-1</button>
</div>
);
});
export default CounterFuncComponent;
看到没?除了包裹方式由装饰器改为直接调用 observer,其余几乎一模一样。这里的 observer 是一个高阶函数,它返回一个新的响应式组件。
四、从类组件迁移到函数组件的实践
实际项目中,可能需要把老的类组件用 observer 的迁移到函数组件。迁移的关键点是:
- 去掉
@observer装饰器,改为在外层调用observer(Component)。 - 将类组件的
render方法体直接变成函数组件的返回内容。 - 如果类组件里有生命周期方法(如
componentDidMount),需要改成useEffect等 hooks。 - 如果访问了
this.props,在函数组件里直接接收 props 参数。 - 注意 MobX 的 store 引用方式:类组件里可能通过
this.props.store获取,函数组件则通过 hooks 或直接读取外部变量。
4.1 转换要点
- 状态管理:类组件中通过
@observable定义的属性,在函数组件里可以用useLocalObservable或外部的observable对象代替。 - 动作:类组件里的
@action方法,迁移后仍可以保持原样,但建议用action.bound或箭头函数避免 this 问题。 - 副作用:类组件的
componentDidUpdate迁移到useEffect,但注意 MobX 的响应式已经处理了渲染,useEffect只用于非渲染副作用。 - 性能:函数组件用
observer时,如果 props 没变但引用的 store 属性变了,组件仍然会重渲染,这是预期行为。如果想进一步优化,可以用React.memo但需要注意observer自己已经做了浅比较优化。
4.2 示例:Todo 列表
下面是一个更实际的例子:Todo 列表。展示如何将类组件转换为函数组件,同时保留 MobX 的响应式。
类组件版本(原始):
// 技术栈:React + MobX(mobx-react)
import React, { Component } from 'react';
import { observable, action, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
class TodoStore {
todos = [];
filter = 'all'; // 'all', 'completed', 'active'
constructor() {
makeObservable(this, {
todos: observable,
filter: observable,
addTodo: action.bound,
toggleTodo: action.bound,
setFilter: action.bound,
});
}
addTodo(text) {
this.todos.push({ text, completed: false });
}
toggleTodo(index) {
this.todos[index].completed = !this.todos[index].completed;
}
setFilter(filter) {
this.filter = filter;
}
get filteredTodos() {
switch (this.filter) {
case 'completed': return this.todos.filter(t => t.completed);
case 'active': return this.todos.filter(t => !t.completed);
default: return this.todos;
}
}
}
const store = new TodoStore();
@observer
class TodoClass extends Component {
handleSubmit = (e) => {
e.preventDefault();
const input = e.target.elements.todoInput;
if (input.value.trim()) {
store.addTodo(input.value);
input.value = '';
}
};
render() {
return (
<div>
<h2>Todo 列表(类组件)</h2>
<form onSubmit={this.handleSubmit}>
<input name="todoInput" placeholder="添加任务" />
<button type="submit">添加</button>
</form>
<div>
<button onClick={() => store.setFilter('all')}>全部</button>
<button onClick={() => store.setFilter('active')}>未完成</button>
<button onClick={() => store.setFilter('completed')}>已完成</button>
</div>
<ul>
{store.filteredTodos.map((todo, index) => (
<li key={index} onClick={() => store.toggleTodo(index)}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</li>
))}
</ul>
</div>
);
}
}
export default TodoClass;
函数组件版本(迁移后):
// 技术栈:React + MobX(mobx-react-lite)
import React, { useCallback } from 'react';
import { observable, action, computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react-lite';
// 复用相同的 store 定义(类定义不变)
class TodoStore {
todos = [];
filter = 'all';
constructor() {
makeObservable(this, {
todos: observable,
filter: observable,
addTodo: action.bound,
toggleTodo: action.bound,
setFilter: action.bound,
filteredTodos: computed, // 用 computed 派生属性
});
}
addTodo(text) {
this.todos.push({ text, completed: false });
}
toggleTodo(index) {
this.todos[index].completed = !this.todos[index].completed;
}
setFilter(filter) {
this.filter = filter;
}
get filteredTodos() {
// 这里是派生数据,用 computed 优化性能
switch (this.filter) {
case 'completed': return this.todos.filter(t => t.completed);
case 'active': return this.todos.filter(t => !t.completed);
default: return this.todos;
}
}
}
const store = new TodoStore();
// 函数组件 + observer
const TodoFunc = observer(() => {
const handleSubmit = useCallback((e) => {
e.preventDefault();
const input = e.target.elements.todoInput;
if (input.value.trim()) {
store.addTodo(input.value);
input.value = '';
}
}, []); // 注意:空依赖可以,因为 store 不会变
return (
<div>
<h2>Todo 列表(函数组件)</h2>
<form onSubmit={handleSubmit}>
<input name="todoInput" placeholder="添加任务" />
<button type="submit">添加</button>
</form>
<div>
<button onClick={() => store.setFilter('all')}>全部</button>
<button onClick={() => store.setFilter('active')}>未完成</button>
<button onClick={() => store.setFilter('completed')}>已完成</button>
</div>
<ul>
{store.filteredTodos.map((todo, index) => (
<li key={index} onClick={() => store.toggleTodo(index)}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</li>
))}
</ul>
</div>
);
});
export default TodoFunc;
迁移后变化:去掉了类,把 render 里的内容直接作为函数返回。表单提交用了 useCallback 保持引用稳定(虽然不是必须,但算个好习惯)。store 实例依然共享,因为 MobX 是响应式的,所有组件都能自动更新。
五、应用场景
- 中大型项目状态共享:多个组件需要读取或修改同一份数据,且数据变化频繁,比如电商购物车、仪表盘实时数据。
- 表单联动:表单字段之间有互相依赖的计算,用 MobX 的 computed 可以自动推导,配合
observer让表单组件实时响应。 - 实时数据展示:比如聊天消息列表、股票行情,数据流从后端 WebSocket 推送到 store,组件自动刷新。
- 复杂状态逻辑:比如多步向导、嵌套的条件渲染,用 MobX 的 action 管理状态变化,比原生 setState 更直观。
六、技术优缺点
优点:
- 学习成本低,写起来像普通 JavaScript 对象,不用学 Redux 的 action/reducer 那一套。
- 粒度细,只有用到的数据变化才重新渲染,性能通常很好(除非滥用)。
- 和 React 配合自然,
observer悄悄完成了依赖追踪,不需要手动优化。 - 支持类组件和函数组件,迁移成本低。
缺点:
- 响应式机制隐藏了数据流,调试时可能难以追踪是谁修改了状态。
- 过度使用可观察属性可能导致不可预期更新,需要遵循“最小可观察”原则。
- 和 React 的并发模式(Concurrent Mode)兼容性还在完善,但基本可用。
- 大型项目中如果 store 设计不合理,容易出现循环依赖或过度耦合。
七、注意事项
- 尽量使用
mobx-react-lite:如果你只写函数组件,用这个轻量包,避免加载类组件的多余代码。 - 不要手动订阅:
observer已经做了依赖收集,不要画蛇添足地再写autorun或reaction来触发组件更新。 - 避免在渲染中修改可观察数据:这是 MobX 的常见陷阱,可能会造成死循环或意外的副作用。所有修改都应该在 action 里做。
- 使用
computed代替 getter:计算属性(computed)有缓存机制,如果派生逻辑复杂,用computed比普通 getter 性能更好。 - 注意对象引用:MobX 追踪的是属性的变化,对于数组或对象,直接赋值给现有属性(如
store.arr[0] = newValue)是不会被检测到的,需要用splice或替换整个数组。但只要你用 action 统一处理,这类问题很少遇到。 - context 传递 store:在复杂应用中,使用 React Context 将 store 传递到子组件,结合
useContext和observer,可以避免全局变量或 props 层层传递。
八、文章总结
observer 是连接 React 和 MobX 的桥梁,它让组件自动响应可观察状态的变化。无论你的项目是用类组件还是函数组件(或者两者混用),observer 都能无缝集成。从类组件迁移到函数组件时,记住把装饰器换成函数包裹,把生命周期换成 hooks,其他逻辑基本保持不变。MobX 的响应式理念非常适合需要频繁更新 UI 的场景,但也提醒我们要有良好的编程习惯,避免滥用可观察属性。希望这篇实践指南能帮你顺畅地在项目里用上 MobX 的 observer,让状态管理不再头疼。
Comments