MobX is already a high-performance state management library, but in real applications, there are still some optimization techniques that can further improve performance. Here are the best practices for MobX performance optimization:
1. Use computed Reasonably
computed's Caching Mechanism
computed properties automatically cache results and only recalculate when dependencies change:
javascriptclass Store { @observable firstName = 'John'; @observable lastName = 'Doe'; @observable age = 30; @computed get fullName() { console.log('Computing fullName'); return `${this.firstName} ${this.lastName}`; } @computed get info() { console.log('Computing info'); return `${this.fullName}, ${this.age} years old`; } } // First access will compute console.log(store.info); // Computing fullName, Computing info // Second access uses cache console.log(store.info); // No output // Modify age, only info recalculates store.age = 31; console.log(store.info); // Computing info
Avoid Side Effects in computed
javascript// Wrong: Side effects in computed @computed get badComputed() { console.log('Side effect!'); // Shouldn't be in computed fetch('/api/data'); // Shouldn't be in computed return this.data; } // Correct: computed should be pure functions @computed get goodComputed() { return this.data.filter(item => item.active); }
2. Optimize observable Usage
Only Use observable for State That Needs Tracking
javascript// Bad practice: All state is observable class Store { @observable config = { apiUrl: 'https://api.example.com', timeout: 5000, retries: 3 }; } // Good practice: Only use observable for state that changes class Store { config = { apiUrl: 'https://api.example.com', timeout: 5000, retries: 3 }; @observable data = []; @observable loading = false; }
Use shallow or deep to Control Observable Depth
javascriptimport { observable, deep, shallow } from 'mobx'; // Deep observable (default) const deepStore = observable({ user: { profile: { name: 'John' } } }); // Shallow observable const shallowStore = observable.shallow({ users: [ { name: 'John' }, { name: 'Jane' } ] }); // Only the array itself is observable, objects in the array are not
3. Batch State Updates
Use runInAction to Batch Updates
javascript// Bad practice: Multiple updates trigger multiple times @action badUpdate() { this.count++; this.name = 'New Name'; this.age++; } // Good practice: Batch updates @action goodUpdate() { runInAction(() => { this.count++; this.name = 'New Name'; this.age++; }); }
Use transaction (MobX 4/5)
javascriptimport { transaction } from 'mobx'; transaction(() => { store.count++; store.name = 'New Name'; store.age++; });
4. Optimize Component Rendering
Use observer Only Where Needed
javascript// Bad practice: All components use observer @observer const Header = () => <h1>My App</h1>; @observer const Footer = () => <footer>© 2024</footer>; // Good practice: Only use observer on components that need to respond to state changes const Header = () => <h1>My App</h1>; const Footer = () => <footer>© 2024</footer>; @observer const Counter = () => <div>{store.count}</div>;
Split Components to Reduce Dependencies
javascript// Bad practice: Component depends on too much state @observer const BadComponent = () => { return ( <div> <div>{store.user.name}</div> <div>{store.user.email}</div> <div>{store.settings.theme}</div> <div>{store.settings.language}</div> <div>{store.data.length}</div> </div> ); }; // Good practice: Split into multiple components @observer const UserInfo = () => { return ( <div> <div>{store.user.name}</div> <div>{store.user.email}</div> </div> ); }; @observer const Settings = () => { return ( <div> <div>{store.settings.theme}</div> <div>{store.settings.language}</div> </div> ); }; @observer const DataCount = () => { return <div>{store.data.length}</div>; };
Use React.memo with observer
javascriptconst PureComponent = React.memo(observer(() => { return <div>{store.count}</div>; }));
5. Avoid Creating New Objects in render
javascript// Bad practice: Create new object on every render @observer const BadComponent = () => { const style = { color: 'red' }; const handleClick = () => console.log('clicked'); return <div style={style} onClick={handleClick}>{store.count}</div>; }; // Good practice: Define outside component const style = { color: 'red' }; const handleClick = () => console.log('clicked'); @observer const GoodComponent = () => { return <div style={style} onClick={handleClick}>{store.count}</div>; };
6. Use trace to Debug Performance Issues
javascriptimport { trace } from 'mobx'; // Track dependencies of computed trace(store.fullName); // Track dependencies of reaction autorun(() => { console.log(store.count); }, { name: 'myReaction' }); // Track component rendering @observer class MyComponent extends React.Component { render() { trace(true); // Track component rendering return <div>{store.count}</div>; } }
7. Optimize with configure
javascriptimport { configure } from 'mobx'; configure({ // Enforce all state modifications in actions enforceActions: 'always', // Use Proxy (if available) useProxies: 'ifavailable', // computed requires reaction to calculate computedRequiresReaction: false, // Disable unnecessary warnings isolateGlobalState: true });
8. Optimize Array Operations
Use splice Instead of Reassignment
javascript// Bad practice: Reassign entire array @action badAddItem(item) { this.items = [...this.items, item]; } // Good practice: Use splice @action goodAddItem(item) { this.items.push(item); }
Use replace for Batch Replacement
javascript@action replaceItems(newItems) { this.items.replace(newItems); }
9. Use reaction Instead of autorun
javascript// Bad practice: autorun executes immediately autorun(() => { console.log(store.count); }); // Good practice: reaction provides finer control reaction( () => store.count, (count) => { console.log(count); }, { fireImmediately: false } );
10. Use when for One-time Conditions
javascript// Bad practice: Use autorun for one-time condition autorun(() => { if (store.data.length > 0) { processData(store.data); } }); // Good practice: Use when when( () => store.data.length > 0, () => processData(store.data) );
11. Avoid Circular Dependencies
javascript// Bad practice: Circular dependencies class StoreA { @observable value = 0; @computed get doubled() { return storeB.value * 2; } } class StoreB { @observable value = 0; @computed get doubled() { return storeA.value * 2; } } // Good practice: Avoid circular dependencies class Store { @observable valueA = 0; @observable valueB = 0; @computed get doubledA() { return this.valueA * 2; } @computed get doubledB() { return this.valueB * 2; } }
12. Clean Up Unnecessary Reactions
javascript// Clean up reaction when component unmounts useEffect(() => { const dispose = autorun(() => { console.log(store.count); }); return () => { dispose(); // Clean up reaction }; }, []);
13. Use MobX DevTools to Analyze Performance
MobX DevTools provides powerful performance analysis features:
- View dependency graphs
- Monitor state changes
- Analyze rendering performance
- Debug computed and reaction
14. Avoid Over-tracking
javascript// Bad practice: Access observable in loop @observer const BadComponent = () => { return ( <div> {store.items.map(item => ( <div key={item.id}> {item.name} - {item.value} </div> ))} </div> ); }; // Good practice: Use computed to preprocess data class Store { @observable items = []; @computed get itemDisplayData() { return this.items.map(item => ({ id: item.id, display: `${item.name} - ${item.value}` })); } } @observer const GoodComponent = () => { return ( <div> {store.itemDisplayData.map(item => ( <div key={item.id}>{item.display}</div> ))} </div> ); };
15. Use makeAutoObservable to Simplify Code
javascript// MobX 6 recommends using makeAutoObservable class Store { count = 0; firstName = 'John'; lastName = 'Doe'; constructor() { makeAutoObservable(this); } get fullName() { return `${this.firstName} ${this.lastName}`; } increment() { this.count++; } }
Summary
Key points for MobX performance optimization:
- Reasonably use computed's caching mechanism
- Only use observable for state that needs tracking
- Batch state updates to reduce trigger times
- Optimize component rendering, reduce unnecessary re-renders
- Avoid creating new objects in render
- Use trace to debug performance issues
- Clean up unnecessary reactions
- Avoid circular dependencies and over-tracking
Following these best practices can build high-performance MobX applications.