5月27日 23:25

What is the purpose and usage of action in MobX?

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