乐闻世界logo
搜索文章和话题

What is the purpose and usage of action in MobX?

2月22日 14:06

In MobX, action is the only recommended way to modify observable state. Using action ensures that state changes are traceable, predictable, and can optimize performance.

Purpose of Action

  1. Batch updates: All state changes within an action are batched, reducing unnecessary recalculations
  2. Traceability: All state changes are concentrated in actions, making debugging and tracking easier
  3. Transactional: State changes within an action are atomic, either all succeed or all fail
  4. Performance optimization: Reduce the number of reaction triggers, improving performance

Action Usage Methods

1. Using Decorators

javascript
import { observable, action } from 'mobx'; class TodoStore { @observable todos = []; @action addTodo(text) { this.todos.push({ text, completed: false }); } @action.bound removeTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); } @action async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); this.todos = data; } }

2. Using makeObservable

javascript
import { makeObservable, observable, action } from 'mobx'; class TodoStore { todos = []; constructor() { makeObservable(this, { todos: observable, addTodo: action, removeTodo: action.bound, fetchTodos: action }); } addTodo(text) { this.todos.push({ text, completed: false }); } removeTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); } async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); this.todos = data; } }

3. Using runInAction

javascript
import { observable, runInAction } from 'mobx'; class TodoStore { todos = []; async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); runInAction(() => { this.todos = data; }); } }

Action Types

1. action

The most basic action decorator, used to wrap state modification methods.

javascript
@action updateTodo(id, updates) { const todo = this.todos.find(t => t.id === id); if (todo) { Object.assign(todo, updates); } }

2. action.bound

Action that automatically binds this, avoiding losing this context in callback functions.

javascript
@action.bound handleClick() { this.count++; } // No need to bind when using <button onClick={this.handleClick}>Click</button>

3. runInAction

Used when modifying state in asynchronous operations.

javascript
async loadData() { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); }

Action Best Practices

1. Always modify state in actions

javascript
// ❌ Wrong: directly modifying state class Store { @observable count = 0; increment() { this.count++; // Not an action } } // ✅ Correct: modifying state in action class Store { @observable count = 0; @action increment() { this.count++; } }

2. Use action.bound for event handlers

javascript
class Component { @observable count = 0; @action.bound handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; } }

3. Use runInAction in asynchronous operations

javascript
@action async fetchUser(id) { this.loading = true; try { const response = await fetch(`/api/users/${id}`); const data = await response.json(); runInAction(() => { this.user = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); } }

4. Reasonably split actions

javascript
// ❌ Too complex action @action handleComplexOperation(data) { this.loading = true; this.data = data; this.filtered = data.filter(item => item.active); this.count = this.filtered.length; this.timestamp = Date.now(); this.loading = false; } // ✅ Split into multiple small actions @action handleComplexOperation(data) { this.setLoading(true); this.setData(data); this.processData(data); this.setLoading(false); } @action setLoading(loading) { this.loading = loading; } @action setData(data) { this.data = data; } @action processData(data) { this.filtered = data.filter(item => item.active); this.count = this.filtered.length; this.timestamp = Date.now(); }

Common Mistakes

1. Directly modifying state in async callbacks

javascript
// ❌ Wrong @action async fetchData() { const data = await fetch('/api/data'); this.data = await data.json(); // Not in action } // ✅ Correct @action async fetchData() { const data = await fetch('/api/data'); runInAction(() => { this.data = data.json(); }); }

2. Forgetting to use action.bound

javascript
// ❌ Wrong: this might be lost class Component { @action handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; } } // ✅ Correct: use action.bound class Component { @action.bound handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; } }

Performance Optimization

  1. Use action for batch updates: Reduce reaction trigger count
  2. Avoid creating actions in loops: Reuse action functions
  3. Use runInAction reasonably: Only use when necessary
  4. Use action.bound: Avoid repeated binding

Summary

  • action is the only recommended way to modify observable state
  • Using action can batch updates, improve performance, and facilitate debugging
  • Prioritize using action.bound for event handlers
  • Use runInAction to modify state in asynchronous operations
  • Reasonably split complex actions to improve maintainability
标签:Mobx