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

What are the differences between autorun, reaction, and when in MobX?

2月21日 15:50

MobX provides three main reaction types: autorun, reaction, and when. Each has different use cases and characteristics, and understanding their differences is crucial for using MobX correctly.

1. autorun

Basic Usage

javascript
import { autorun } from 'mobx'; const store = observable({ count: 0 }); autorun(() => { console.log(`Count is: ${store.count}`); }); store.count++; // Output: Count is: 1 store.count++; // Output: Count is: 2

Characteristics

  • Immediate execution: autorun executes immediately upon creation
  • Automatic tracking: Automatically tracks all observables accessed in the function
  • Automatic re-execution: Automatically re-executes when tracked observables change
  • No return value: Cannot return values, mainly used for side effects

Use Cases

  • Logging
  • Data persistence
  • Syncing state to localStorage
  • Sending analytics data

Example: Logging

javascript
autorun(() => { console.log('State changed:', toJS(store)); });

Example: Persisting to localStorage

javascript
autorun(() => { localStorage.setItem('appState', JSON.stringify(toJS(store))); });

2. reaction

Basic Usage

javascript
import { reaction } from 'mobx'; const store = observable({ count: 0, name: 'John' }); reaction( () => store.count, // Tracking function (count, reaction) => { // Effect function console.log(`Count changed to: ${count}`); }, { fireImmediately: false } // Configuration options ); store.count++; // Output: Count changed to: 1 store.name = 'Jane'; // Won't trigger, only tracks count

Characteristics

  • Delayed execution: By default, doesn't execute immediately
  • Precise control: Can precisely specify which observables to track
  • Compare changes: Can compare old and new values
  • Configurable: Provides multiple configuration options

Configuration Options

javascript
reaction( () => store.count, (count, prevCount, reaction) => { console.log(`Count changed from ${prevCount} to ${count}`); }, { fireImmediately: true, // Execute immediately delay: 100, // Delay execution equals: (a, b) => a === b, // Custom comparison function name: 'myReaction' // Debug name } );

Use Cases

  • Need precise control over tracking scope
  • Need to compare old and new values
  • Need delayed execution
  • Complex side effect logic
javascript
reaction( () => store.searchQuery, (query) => { // Delay search by 300ms debounce(() => { performSearch(query); }, 300); }, { delay: 300 } );

Example: Comparing Old and New Values

javascript
reaction( () => store.items, (items, prevItems) => { const added = items.filter(item => !prevItems.includes(item)); const removed = prevItems.filter(item => !items.includes(item)); console.log('Added:', added); console.log('Removed:', removed); }, { equals: comparer.structural } // Deep comparison );

3. when

Basic Usage

javascript
import { when } from 'mobx'; const store = observable({ loaded: false, data: null }); when( () => store.loaded, // Condition function () => { // Effect function console.log('Data loaded:', store.data); } ); store.loaded = true; store.data = { name: 'John' }; // Output: Data loaded: { name: 'John' }

Characteristics

  • One-time execution: Executes only once when condition is met
  • Automatic cleanup: Automatically cleans up after execution
  • Cancellable: Can be manually cancelled
  • Returns disposer: Returns a cleanup function

Use Cases

  • Wait for a condition to be met before executing
  • Initialization logic
  • One-time side effects

Example: Waiting for Data to Load

javascript
when( () => store.isLoaded, () => { initializeApp(); } );

Example: Cancellable when

javascript
const dispose = when( () => store.userLoggedIn, () => { showWelcomeMessage(); } ); // If you need to cancel dispose();

Example: Timeout Handling

javascript
const dispose = when( () => store.dataLoaded, () => { console.log('Data loaded successfully'); } ); // Cancel after 5 seconds setTimeout(() => { dispose(); console.log('Loading timeout'); }, 5000);

Comparison of the Three

Featureautorunreactionwhen
Execution timingImmediateDelayed (default)When condition is met
Execution countMultipleMultipleOnce
Tracking scopeAuto-track all dependenciesPrecise tracking scopeOnly tracks condition
Return valueNonedisposerdisposer
Use casesLogging, persistenceComplex side effects, comparing valuesInitialization, one-time operations

Selection Guide

Use autorun when:

  • Need immediate execution
  • Need to track all dependencies
  • For simple side effects
  • Don't need to compare old and new values
javascript
autorun(() => { document.title = store.pageTitle; });

Use reaction when:

  • Need precise control over tracking scope
  • Need to compare old and new values
  • Need delayed execution
  • Need complex side effect logic
javascript
reaction( () => store.userId, (userId, prevUserId) => { if (userId !== prevUserId) { loadUserData(userId); } } );

Use when when:

  • Need to wait for a condition
  • Only need to execute once
  • For initialization logic
  • Need cancellable operations
javascript
when( () => store.initialized, () => { startApp(); } );

Performance Considerations

1. Avoid Over-tracking

javascript
// Bad practice: autorun tracks too much autorun(() => { console.log(store.user.name, store.user.email, store.user.age); }); // Good practice: reaction tracks precisely reaction( () => store.user.name, (name) => { console.log(name); } );

2. Clean Up Promptly

javascript
// Clean up when component unmounts useEffect(() => { const dispose = autorun(() => { console.log(store.count); }); return () => { dispose(); // Clean up reaction }; }, []);

3. Use comparer to Optimize Comparison

javascript
import { comparer } from 'mobx'; reaction( () => store.items, (items) => { console.log('Items changed'); }, { equals: comparer.structural } // Deep comparison, avoid unnecessary updates );

Common Pitfalls

1. Side Effects in reaction

javascript
// Bad practice: Side effects in tracking function reaction( () => { console.log('Side effect!'); // Shouldn't be in tracking function return store.count; }, (count) => { console.log(count); } ); // Good practice: Tracking function should be pure reaction( () => store.count, (count) => { console.log('Side effect:', count); // Side effects in effect function } );

2. Forgetting to Clean Up reaction

javascript
// Bad practice: Forgetting to clean up useEffect(() => { autorun(() => { console.log(store.count); }); // No cleanup function }, []); // Good practice: Clean up reaction useEffect(() => { const dispose = autorun(() => { console.log(store.count); }); return () => dispose(); }, []);

3. Abusing autorun

javascript
// Bad practice: Using autorun for one-time operations autorun(() => { if (store.initialized) { initializeApp(); // Will execute multiple times } }); // Good practice: Use when for one-time operations when( () => store.initialized, () => { initializeApp(); // Executes only once } );

Summary

Understanding the differences and use cases of autorun, reaction, and when is key to mastering MobX:

  • autorun: For simple, immediately executing side effects
  • reaction: For complex side effects that need precise control and value comparison
  • when: For one-time operations that wait for a condition to be met

Choosing and using these reaction types correctly can build more efficient and maintainable MobX applications.

标签:Mobx