Action动作 #
Action 是 MobX 中修改状态的唯一推荐方式。使用 Action 可以让状态变化变得可追踪、可调试,并且支持批量更新。
什么是Action? #
Action 是一个函数,它包含状态修改的逻辑。MobX 会自动追踪 Action 内的状态修改,并在 Action 执行完成后触发更新。
text
┌─────────────────────────────────────────────┐
│ Action │
│ │
│ ┌─────────────────────────────────┐ │
│ │ state.count++ │ │
│ │ state.name = 'new name' │ │
│ │ state.items.push(item) │ │
│ └─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 批量更新,只触发一次渲染 │
└─────────────────────────────────────────────┘
定义Action #
使用makeAutoObservable #
javascript
import { makeAutoObservable } from 'mobx';
class CounterStore {
count = 0;
constructor() {
makeAutoObservable(this);
}
// 自动识别为 action
increment() {
this.count++;
}
decrement() {
this.count--;
}
// 箭头函数也会被识别为 action
reset = () => {
this.count = 0;
};
}
使用makeObservable #
javascript
import { makeObservable, observable, action } from 'mobx';
class CounterStore {
count = 0;
constructor() {
makeObservable(this, {
count: observable,
increment: action,
decrement: action,
reset: action.bound
});
}
increment() {
this.count++;
}
decrement() {
this.count--;
}
reset() {
this.count = 0;
}
}
使用action函数 #
javascript
import { observable, action } from 'mobx';
const state = observable({
count: 0
});
// 使用 action 包装
const increment = action(() => {
state.count++;
});
// 带名称的 action(便于调试)
const decrement = action('decrement', () => {
state.count--;
});
Action的特点 #
1. 批量更新 #
Action 内的多个状态修改只会触发一次更新:
javascript
class Store {
firstName = '';
lastName = '';
constructor() {
makeAutoObservable(this);
}
// 批量更新,只触发一次渲染
setName(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
2. 事务性 #
Action 是原子操作,要么全部成功,要么全部失败:
javascript
class Store {
balance = 100;
transactions = [];
constructor() {
makeAutoObservable(this);
}
transfer(amount, toAccount) {
if (this.balance < amount) {
throw new Error('Insufficient balance');
}
// 如果这里抛出异常,上面的修改会回滚
this.balance -= amount;
this.transactions.push({
type: 'transfer',
amount,
to: toAccount,
date: new Date()
});
}
}
3. 可追踪 #
Action 有名称,便于调试:
javascript
import { action } from 'mobx';
const increment = action('increment counter', () => {
state.count++;
});
// 在 DevTools 中可以看到 "increment counter" 的调用记录
action.bound #
action.bound 会自动绑定 this:
javascript
import { makeAutoObservable, action } from 'mobx';
class Store {
count = 0;
constructor() {
makeAutoObservable(this, {
increment: action.bound
});
}
increment() {
this.count++;
}
}
const store = new Store();
const { increment } = store;
increment(); // this 正确绑定
或者使用 autoBind 选项:
javascript
class Store {
count = 0;
constructor() {
makeAutoObservable(this, {}, { autoBind: true });
}
increment() {
this.count++;
}
}
强制使用Action #
可以通过配置强制所有状态修改都在 Action 中进行:
javascript
import { configure } from 'mobx';
configure({
enforceActions: 'always' // 强制使用 action
});
配置选项:
| 值 | 说明 |
|---|---|
'never' |
允许在任何地方修改状态(默认) |
'observed' |
只在观察到的状态下强制使用 action |
'always' |
始终强制使用 action |
javascript
import { configure, makeAutoObservable } from 'mobx';
configure({ enforceActions: 'always' });
class Store {
count = 0;
constructor() {
makeAutoObservable(this);
}
increment() {
this.count++; // 正确:在 action 中
}
}
const store = new Store();
store.count++; // 错误:不在 action 中
完整示例 #
Todo Store #
javascript
import { makeAutoObservable } from 'mobx';
class TodoStore {
todos = [];
filter = 'all';
constructor() {
makeAutoObservable(this);
}
// 添加 todo
addTodo(title) {
this.todos.push({
id: Date.now(),
title,
completed: false,
createdAt: new Date()
});
}
// 删除 todo
removeTodo(id) {
const index = this.todos.findIndex(t => t.id === id);
if (index > -1) {
this.todos.splice(index, 1);
}
}
// 切换完成状态
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
}
// 编辑 todo
editTodo(id, title) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.title = title;
}
}
// 清除已完成的 todo
clearCompleted() {
this.todos = this.todos.filter(t => !t.completed);
}
// 全部标记为完成/未完成
toggleAll(completed) {
this.todos.forEach(todo => {
todo.completed = completed;
});
}
// 设置过滤器
setFilter(filter) {
this.filter = filter;
}
}
export default new TodoStore();
用户认证Store #
javascript
import { makeAutoObservable } from 'mobx';
class AuthStore {
user = null;
token = null;
loading = false;
error = null;
constructor() {
makeAutoObservable(this);
}
// 登录
async login(email, password) {
this.loading = true;
this.error = null;
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
if (!response.ok) {
throw new Error('Login failed');
}
const data = await response.json();
this.user = data.user;
this.token = data.token;
this.error = null;
// 保存到 localStorage
localStorage.setItem('token', data.token);
return data;
} catch (error) {
this.error = error.message;
throw error;
} finally {
this.loading = false;
}
}
// 登出
logout() {
this.user = null;
this.token = null;
this.error = null;
localStorage.removeItem('token');
}
// 检查登录状态
async checkAuth() {
const token = localStorage.getItem('token');
if (!token) return;
this.loading = true;
try {
const response = await fetch('/api/me', {
headers: { Authorization: `Bearer ${token}` }
});
if (response.ok) {
const user = await response.json();
this.user = user;
this.token = token;
} else {
this.logout();
}
} catch (error) {
this.logout();
} finally {
this.loading = false;
}
}
// 更新用户信息
updateUser(updates) {
if (this.user) {
Object.assign(this.user, updates);
}
}
// 清除错误
clearError() {
this.error = null;
}
}
export default new AuthStore();
最佳实践 #
1. Action命名规范 #
javascript
class Store {
// 好的命名:动词开头
addItem() { }
removeItem() { }
updateItem() { }
toggleItem() { }
clearItems() { }
// 避免:名词或不清晰的命名
item() { } // 不好
data() { } // 不好
process() { } // 不好
}
2. 单一职责 #
javascript
class Store {
// 好:每个 action 做一件事
setName(name) {
this.name = name;
}
setAge(age) {
this.age = age;
}
// 如果需要同时修改多个状态,创建新的 action
updateProfile({ name, age }) {
this.setName(name);
this.setAge(age);
}
}
3. 参数验证 #
javascript
class Store {
setAge(age) {
if (typeof age !== 'number' || age < 0) {
throw new Error('Invalid age');
}
this.age = age;
}
setEmail(email) {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
throw new Error('Invalid email');
}
this.email = email;
}
}
4. 返回值 #
javascript
class Store {
items = [];
// 返回新创建的项
addItem(item) {
const newItem = { ...item, id: Date.now() };
this.items.push(newItem);
return newItem;
}
// 返回操作结果
removeItem(id) {
const index = this.items.findIndex(item => item.id === id);
if (index === -1) {
return false;
}
this.items.splice(index, 1);
return true;
}
}
总结 #
Action 的核心要点:
| 特点 | 说明 |
|---|---|
| 批量更新 | 多个修改只触发一次更新 |
| 事务性 | 原子操作,保证数据一致性 |
| 可追踪 | 便于调试和日志记录 |
| 可选强制 | 可配置强制使用 action |
继续学习 action.bound,了解如何自动绑定方法中的 this。
最后更新:2026-03-28