MobX provides several tools for creating and managing observable state, including makeObservable, makeAutoObservable, and decorators. Understanding their differences and use cases is crucial for using MobX correctly.
1. makeObservable
Basic Usage
javascriptimport { makeObservable, observable, computed, action } from 'mobx'; class Store { count = 0; firstName = 'John'; lastName = 'Doe'; constructor() { makeObservable(this, { count: observable, firstName: observable, lastName: observable, fullName: computed, increment: action, decrement: action.bound }); } get fullName() { return `${this.firstName} ${this.lastName}`; } increment() { this.count++; } decrement = () => { this.count--; }; }
Characteristics
- Explicit declaration: Need to explicitly declare the type of each property
- High flexibility: Can precisely control the behavior of each property
- Type-safe: Good integration with TypeScript
- Requires configuration: Needs to be called in the constructor
Use Cases
- Need precise control over each property's behavior
- Using TypeScript
- Need custom configuration
Advanced Usage
javascriptclass Store { data = []; loading = false; error = null; constructor() { makeObservable(this, { data: observable, loading: observable, error: observable, itemCount: computed, fetchData: action, clearData: action }, { autoBind: true }); // Auto-bind this } get itemCount() { return this.data.length; } async fetchData() { this.loading = true; try { const response = await fetch('/api/data'); this.data = await response.json(); } catch (error) { this.error = error.message; } finally { this.loading = false; } } clearData() { this.data = []; this.error = null; } }
2. makeAutoObservable
Basic Usage
javascriptimport { makeAutoObservable } from 'mobx'; class Store { count = 0; firstName = 'John'; lastName = 'Doe'; constructor() { makeAutoObservable(this); } get fullName() { return `${this.firstName} ${this.lastName}`; } increment() { this.count++; } decrement = () => { this.count--; }; }
Characteristics
- Automatic inference: Automatically infers the type of properties
- Concise: Less boilerplate code
- Smart inference:
- getter → computed
- method → action
- field → observable
- Overridable: Can override default inference
Use Cases
- Rapid development
- Don't need precise control
- Code simplicity is priority
Advanced Usage
javascriptclass Store { data = []; loading = false; error = null; _internalState = {}; // Properties starting with underscore won't be auto-inferred constructor() { makeAutoObservable(this, { // Override default inference data: observable.deep, fetchData: flow, _internalState: false // Don't make it observable }); } get itemCount() { return this.data.length; } fetchData = flow(function* () { this.loading = true; try { const response = yield fetch('/api/data'); this.data = yield response.json(); } catch (error) { this.error = error.message; } finally { this.loading = false; } }); }
3. Decorators
Basic Usage
javascriptimport { observable, computed, action } from 'mobx'; class Store { @observable count = 0; @observable firstName = 'John'; @observable lastName = 'Doe'; @computed get fullName() { return `${this.firstName} ${this.lastName}`; } @action increment() { this.count++; } @action.bound decrement = () => { this.count--; }; }
Characteristics
- Declarative: Uses decorator syntax
- Concise: More readable code
- Requires configuration: Needs Babel or TypeScript support
- Optional in MobX 6: Decorators are no longer required
Use Cases
- Project already has decorator support configured
- Prefer decorator syntax
- Need compatibility with MobX 4/5
Advanced Usage
javascriptimport { observable, computed, action, flow } from 'mobx'; class Store { @observable data = []; @observable loading = false; @observable error = null; @computed get itemCount() { return this.data.length; } @action async fetchData() { this.loading = true; try { const response = await fetch('/api/data'); this.data = await response.json(); } catch (error) { this.error = error.message; } finally { this.loading = false; } } @action.bound clearData() { this.data = []; this.error = null; } }
Comparison of the Three
| Feature | makeObservable | makeAutoObservable | Decorators |
|---|---|---|---|
| Declaration style | Explicit config | Auto inference | Decorators |
| Code volume | More | Less | Less |
| Flexibility | High | Medium | High |
| TypeScript support | Good | Good | Good |
| Configuration required | Yes | No | Need Babel/TS |
| MobX 6 recommended | Yes | Yes | Optional |
Selection Guide
Use makeObservable when:
- Need precise control over each property's behavior
- Using TypeScript
- Need custom configuration
- Need to override default behavior
javascriptclass Store { data = []; constructor() { makeObservable(this, { data: observable.shallow, // Shallow observable itemCount: computed, fetchData: action }); } }
Use makeAutoObservable when:
- Rapid development
- Don't need precise control
- Code simplicity is priority
- Using MobX 6
javascriptclass Store { data = []; constructor() { makeAutoObservable(this); } }
Use decorators when:
- Project already has decorator support configured
- Prefer decorator syntax
- Need compatibility with MobX 4/5
javascriptclass Store { @observable data = []; }
Integration with TypeScript
makeObservable + TypeScript
typescriptimport { makeObservable, observable, computed, action } from 'mobx'; class Store { count: number = 0; firstName: string = 'John'; lastName: string = 'Doe'; constructor() { makeObservable<Store>(this, { count: observable, firstName: observable, lastName: observable, fullName: computed, increment: action }); } get fullName(): string { return `${this.firstName} ${this.lastName}`; } increment(): void { this.count++; } }
makeAutoObservable + TypeScript
typescriptimport { makeAutoObservable } from 'mobx'; class Store { count: number = 0; firstName: string = 'John'; lastName: string = 'Doe'; constructor() { makeAutoObservable(this); } get fullName(): string { return `${this.firstName} ${this.lastName}`; } increment(): void { this.count++; } }
Decorators + TypeScript
typescriptimport { observable, computed, action } from 'mobx'; class Store { @observable count: number = 0; @observable firstName: string = 'John'; @observable lastName: string = 'Doe'; @computed get fullName(): string { return `${this.firstName} ${this.lastName}`; } @action increment(): void { this.count++; } }
Best Practices
1. MobX 6 Recommends makeAutoObservable
javascript// Recommended class Store { count = 0; constructor() { makeAutoObservable(this); } } // Can also use makeObservable class Store { count = 0; constructor() { makeObservable(this, { count: observable }); } }
2. Use makeObservable to Override Default Behavior
javascriptclass Store { data = []; constructor() { makeAutoObservable(this, { data: observable.shallow // Override default deep observable }); } }
3. Use action.bound or autoBind
javascriptclass Store { count = 0; constructor() { makeAutoObservable(this, {}, { autoBind: true }); } increment() { this.count++; // this auto-bound } }
4. Private Property Handling
javascriptclass Store { data = []; _privateData = []; // Starts with underscore, won't be auto-inferred constructor() { makeAutoObservable(this, { _privateData: false // Explicitly don't make it observable }); } }
Common Issues
1. Decorators Not Working
Ensure:
- Configured Babel or TypeScript decorator support
- Using correct decorator syntax
- MobX version supports decorators
2. makeAutoObservable Inference Error
javascript// If inference is wrong, use makeObservable for explicit declaration class Store { data = []; constructor() { makeAutoObservable(this, { data: observable.shallow // Explicit declaration }); } }
3. TypeScript Type Errors
javascript// Use generic parameters class Store { count = 0; constructor() { makeObservable<Store>(this, { count: observable }); } }
Summary
In MobX 6, it's recommended to use makeAutoObservable for rapid development and makeObservable for precise control. Decorators are still available but no longer required. The choice depends on project requirements and personal preference.