核心思想
Mobx 的核心思想是围绕响应式状态管理展开,通过 可观察对象(Observables) 储存状态、动作 (Actions) 修改状态、响应机制 自动同步状态变化到依赖项(如 React 组件、计算值、副作用),实现“状态变化自动驱动视图几衍生逻辑更新”的见解流程。
一、可观察对象(Observables):状态的“响应式容器”
可观察对象是 Mobx 状态管理的基础,用于定义应用中需要跟踪变化的状态数据(如组件的 props、state 全局 store 中的属性)。通过 @observable 装饰器或 observable() 函数标记, Mobx 会自动追踪这些数据的读取和修改,当数据变化时通知所有依赖它的逻辑(如组件渲染、计算值更新)。
-
类属性: 通过
@observable装饰器标记类中的状态属性(需启用装饰器语法,或用decorate工具代替)import { observable, decorate } from 'mobx';
class Todo {
id = Math.random();
@observable
title = ''; // 可观察标题
@observable
finished = false; // 可观察的完成状态
}
// ES 5 环境替代方案
class TodoE {
id = Math.random();
title = '';
finished = false;
}
decorate(Todo, {
title: observable,
finished: observable,
}); -
原始值/引用值:
observable()函数可将原始值(如数字、字符串)或引用值(如对象、数组、Map)转换为可观察对象import { observable } from 'mobx';
// 原始值的可观察容器,通过 `set`/`get` 修改/读取
const count = observable.box(0);
// 数组的可观察版本,修改元素会触发通知
const todos = observable(['学习雷锋', '好榜样']);
// 对象的可观察版本,修改属性会触发通知
const user = observable({
name: 'Tom',
age: 86,
});
核心特性:
- 自动追踪:Mobx 通过
Proxy(MobX 5+) 或Object.defineProperty(Mobx 4-)拦截可观察对象的 读取(getter) 与 修改(setter) 操作,无需手动调用setState或 `forceUpdate - 感染性:默认情况下,可观察对象的所有属性购会递归转换成可观察(如
observable({a: 1, b : {c : 2}})中的b.c也会被观察),可通过modifiers(如asFlat、asReference)调整这一行为
二、工作(action):状态修改的“唯一入口”
动作是 Mobx 中 修改可观察对象状态 的唯一合法方式,用于封装所有改变状态的逻辑(如用户点击事件、API 请求后的数据更新)。通过 @action 装饰器或 action() 标记,Mobx 会追踪动作中的状态修改,并确保 状态变化的原子性 (即动作执行完成后,所有依赖状态才会更新)。
-
类方法: 通过
@action装饰器标记类中的状态修改方法import { observable, action, decorate } from 'mobx';
class TodoStore {
@observable
todos = [];
// 添加动作
@action
addTodo(title) {
this.todos.push({
id: Math.random(),
title,
finished: false,
});
}
// 标记完成动作
@action
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.finished = !todo.finished;
}
}
}
// ES5 环境替代方案
class TodoStoreES5 {
todos = [];
addTodo(title) {
this.todos.push({
id: Math.random(),
title,
finished: false,
});
}
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.finished = !todo.finished;
}
}
}
decorate(todoStoreES5, {
todos: observable,
addTodo: action,
toggleTodo: action,
}); -
一次性动作:
runInAction()函数用于在非动作上下文(如异步回调、事件处理函数)中临时创建动作,确保状态修改的非法性import { action, runInaction } from 'mobx';
class UserStore {
@observable
user = null;
async fetchUser(id) {
const response = await fetch(`api/users/${id}`);
const user = await response.json();
// 必须在动作中修改动态(异步回调不是动作)
runInAction(() => {
this.user = user;
});
}
}
核心特性:
- 原子性:动作执行过程中,Mobx 会缓存所有状态修改,知道动作完成完成后再统一通知依赖项,避免中间状态导致的 UI 闪烁
- 严格模式:通过
configure({ enforceActions: "always"})启用严格模式后,禁止在动作外修改状态,强制开发者封装状态修改逻辑,提升代码可维护性
三、响应式机制:状态与视图的“自动同步桥梁”
响应式机制是 Mobx 的核心灵魂,通过 自动追踪依赖 与 触发更新 ,实现“状态变化 ➞ 视图/衍生逻辑自动更新”的流程。其核心逻辑是:
- 依赖收集: 当计算值(computed)或反应(reactions,如 React 组件渲染)读取可观察对象时,Mobx 会记录“该计算值/反应依赖于哪些可观察对象”
- 变化通知:当可观察对象被动作修改后,Mobx 会通知所有依赖他的计算值/反应
- 更新触发:计算值会自动重新计算(如
unfinishedTodoCount会根据todos的变化重新计算统计),返回会执行副作用(如 React 组件重新渲染、发送网络请求)
1. 响应式机制 -- 计算值
通过 @computed 状态器或 computed() 函数定义,基于可观察对象自动计算衍生值(如统计未完成的 todo 数量)。计算值具有惰性求值特性(仅当依赖的可观察对象变化且被使用时才会重新计算)
import { observable, computed, action } from 'mobx';
class TodoStore {
@observable
todos = [];
// 未完成的 todo 数量(依赖 todos )
@computed
get unfinishedTodoCount() {
return this.todos.filter(todo => !todo.finished).length;
}
@action
addTodo(title) {
this.todos.push({
id: Math.random(),
title,
finished: false,
});
}
}
2. 响应式机制 -- 反应
通过 autorun()、reaction() 或 when() 函数定义,用于处理 副作用 (如更新 UI、发动网络请求)。其中, autorun() 是最常用的反应类型,会自动追踪其回调函数中的可观察对象依赖,并在依赖变化时执行回调。
import { observable, autorun } from 'mobx';
const count = observable.box(0);
// 反应:当 count 变化时,打印日志(依赖 count)
autorun(() => {
console.log(`Count is now: ${count.get()}`);
});
count.set(1); // 输出:count is now:1
count.set(2); // 输出:count is now:2
3. 响应式机制 -- 与 React 集成
通过 mobx-react 库的 observer 高阶组件或 useObserverHook,将 React 组件转化为“响应式组件”。当组件依赖的可观察对象变化时,observer 会自动触发组件重新渲染。
import { observer } from 'mobx-react';
import { TodoStore } from './store/TodoStore';
// 响应式组件:依赖 TodoStore 的 todos 和 unfinishedTodoCount
const TodoList = observer(({store}) => {
<div>
<h2>未办事件:{{store.unfinishedTodoCount }}</h2>
<ul>
{store.todos.map(todo => (
<li
key={todo.id}
style={{textDecoration: todo.finished
?
'line-through'
:
'none'}}>
{todo.title}
</li>))}
</ul>
</div>
});