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