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

What are the purpose and use cases of computed in MobX?

2月22日 14:05

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

  1. Automatic caching: Calculation results are cached to avoid repeated calculations
  2. Lazy evaluation: Only calculated when accessed
  3. Automatic dependency tracking: Automatically tracks dependent observable state
  4. Automatic updates: Automatically recalculates when dependent state changes
  5. Pure function: computed should be pure functions without side effects

Computed Usage Methods

1. Using Decorators

javascript
import { 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

javascript
import { 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)

javascript
import { 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

javascript
import { 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

javascript
class 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

javascript
class 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

javascript
class 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

FeatureComputedReaction
PurposeCalculate derived valuesExecute side effects
Return valueReturns calculation resultNo return value
CachingAutomatic cachingNo caching
Trigger timingWhen accessedImmediately when dependencies change
Use casesData transformation, filtering, aggregationLogging, API calls, DOM operations

Performance Optimization

  1. Use computed reasonably: Avoid overuse, only use when caching is needed
  2. Avoid complex calculations: If calculation is very expensive, consider using memoization
  3. Use computed chains: Split complex calculations into multiple small computed values
  4. 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
标签:Mobx