In MobX, computed is a derived value that automatically updates based on observable state, similar to Vue's computed properties. computed values cache calculation results and only recalculate when dependent observable state changes.
Computed Features
- Automatic caching: Calculation results are cached to avoid repeated calculations
- Lazy evaluation: Only calculated when accessed
- Automatic dependency tracking: Automatically tracks dependent observable state
- Automatic updates: Automatically recalculates when dependent state changes
- Pure function: computed should be pure functions without side effects
Computed Usage Methods
1. Using Decorators
javascriptimport { observable, computed } from 'mobx'; class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @computed get activeTodos() { return this.todos.filter(todo => !todo.completed); } @computed get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } } }
2. Using makeObservable
javascriptimport { makeObservable, observable, computed } from 'mobx'; class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, activeTodos: computed, filteredTodos: computed }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } get activeTodos() { return this.todos.filter(todo => !todo.completed); } get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } } }
3. Using makeAutoObservable (Recommended)
javascriptimport { makeAutoObservable } from 'mobx'; class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } get activeTodos() { return this.todos.filter(todo => !todo.completed); } get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } } }
4. Using computed Function
javascriptimport { observable, computed } from 'mobx'; const store = observable({ todos: [], filter: 'all' }); const completedTodos = computed(() => store.todos.filter(todo => todo.completed) ); const activeTodos = computed(() => store.todos.filter(todo => !todo.completed) ); console.log(completedTodos.get()); // Get computed value
Computed Configuration Options
1. name
Set a name for computed to facilitate debugging.
javascript@computed({ name: 'completedTodos' }) get completedTodos() { return this.todos.filter(todo => todo.completed); }
2. equals
Custom comparison function to determine if recalculation is needed.
javascript@computed({ equals: (a, b) => a.length === b.length && a.every(item => b.includes(item)) }) get filteredTodos() { return this.todos.filter(todo => todo.completed); }
3. keepAlive
Keep the computed value active even if not accessed.
javascript@computed({ keepAlive: true }) get expensiveComputation() { return this.largeArray.reduce((sum, item) => sum + item.value, 0); }
Computed Best Practices
1. Use computed to cache complex calculations
javascriptclass Store { @observable items = []; @computed get totalValue() { return this.items.reduce((sum, item) => sum + item.value, 0); } @computed get averageValue() { return this.items.length > 0 ? this.totalValue / this.items.length : 0; } }
2. Avoid side effects in computed
javascript// ❌ Wrong: side effects in computed @computed get filteredItems() { console.log('Filtering items'); // Side effect return this.items.filter(item => item.active); } // ✅ Correct: use reaction for side effects @computed get filteredItems() { return this.items.filter(item => item.active); } constructor() { reaction( () => this.filteredItems, (items) => console.log('Filtered items:', items) ); }
3. Reasonably use computed chains
javascriptclass Store { @observable products = []; @observable category = 'all'; @observable minPrice = 0; @observable maxPrice = Infinity; @computed get filteredByCategory() { return this.category === 'all' ? this.products : this.products.filter(p => p.category === this.category); } @computed get filteredByPrice() { return this.filteredByCategory.filter( p => p.price >= this.minPrice && p.price <= this.maxPrice ); } @computed get sortedProducts() { return [...this.filteredByPrice].sort((a, b) => a.price - b.price); } }
4. Use computed for form validation
javascriptclass FormStore { @observable username = ''; @observable email = ''; @observable password = ''; @computed get usernameError() { if (this.username.length < 3) { return 'Username must be at least 3 characters'; } return ''; } @computed get emailError() { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(this.email)) { return 'Invalid email format'; } return ''; } @computed get passwordError() { if (this.password.length < 8) { return 'Password must be at least 8 characters'; } return ''; } @computed get isValid() { return !this.usernameError && !this.emailError && !this.passwordError; } }
Computed vs Reaction
| Feature | Computed | Reaction |
|---|---|---|
| Purpose | Calculate derived values | Execute side effects |
| Return value | Returns calculation result | No return value |
| Caching | Automatic caching | No caching |
| Trigger timing | When accessed | Immediately when dependencies change |
| Use cases | Data transformation, filtering, aggregation | Logging, API calls, DOM operations |
Performance Optimization
- Use computed reasonably: Avoid overuse, only use when caching is needed
- Avoid complex calculations: If calculation is very expensive, consider using memoization
- Use computed chains: Split complex calculations into multiple small computed values
- Avoid creating new objects in computed: May cause unnecessary recalculation
Common Mistakes
1. Modifying state in computed
javascript// ❌ Wrong: modifying state in computed @computed get filteredItems() { this.lastFilterTime = Date.now(); // Modifying state return this.items.filter(item => item.active); } // ✅ Correct: use reaction for side effects @computed get filteredItems() { return this.items.filter(item => item.active); } constructor() { reaction( () => this.filteredItems, () => { this.lastFilterTime = Date.now(); } ); }
2. Using async operations in computed
javascript// ❌ Wrong: async operations in computed @computed async get userData() { const response = await fetch('/api/user'); return response.json(); } // ✅ Correct: use reaction for async operations constructor() { reaction( () => this.userId, async (id) => { const response = await fetch(`/api/user/${id}`); runInAction(() => { this.userData = await response.json(); }); } ); }
Summary
- computed is a derived value that automatically updates based on observable state
- computed automatically caches calculation results to improve performance
- computed should be pure functions without side effects
- Reasonably use computed chains for complex calculations
- Avoid modifying state or using async operations in computed
- Use reaction for side effects and async operations