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
- Batch updates: All state changes within an action are batched, reducing unnecessary recalculations
- Traceability: All state changes are concentrated in actions, making debugging and tracking easier
- Transactional: State changes within an action are atomic, either all succeed or all fail
- Performance optimization: Reduce the number of reaction triggers, improving performance
Action Usage Methods
1. Using Decorators
javascriptimport { 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
javascriptimport { 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
javascriptimport { 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.
javascriptasync 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
javascriptclass 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
- Use action for batch updates: Reduce reaction trigger count
- Avoid creating actions in loops: Reuse action functions
- Use runInAction reasonably: Only use when necessary
- 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