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

What are the common problems and solutions in MobX?

2月21日 15:50

MobX is a powerful state management library, but developers may encounter some common issues during use. Understanding these problems and their solutions can help developers use MobX more effectively.

1. Component Not Updating

Problem Description

Component is wrapped with observer but doesn't update when state changes.

Common Causes and Solutions

Cause 1: Accessing Plain Object Instead of Observable

javascript
// Wrong const store = { count: 0 }; @observer class Counter extends React.Component { render() { return <div>{store.count}</div>; // Won't update } } // Correct import { observable } from 'mobx'; const store = observable({ count: 0 }); @observer class Counter extends React.Component { render() { return <div>{store.count}</div>; // Will update } }

Cause 2: Modifying State Outside Action (MobX 6)

javascript
// Wrong (MobX 6) class Store { @observable count = 0; increment() { this.count++; // Not in action, will throw error } } // Correct class Store { @observable count = 0; @action increment() { this.count++; // In action } }

Cause 3: Creating New Objects in render

javascript
// Wrong @observer class Component extends React.Component { render() { const style = { color: 'red' }; // Creates new object every render return <div style={style}>{store.count}</div>; } } // Correct const style = { color: 'red' }; // Define outside component @observer class Component extends React.Component { render() { return <div style={style}>{store.count}</div>; } }

2. Performance Issues

Problem Description

Application performance degrades, components re-render frequently.

Common Causes and Solutions

Cause 1: Over-tracking

javascript
// Wrong: Accessing observable in loop @observer class List extends React.Component { render() { return ( <div> {store.items.map(item => ( <div key={item.id}> {item.name} - {item.value} - {item.description} </div> ))} </div> ); } } // Correct: 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} - ${item.description}` })); } } @observer class List extends React.Component { render() { return ( <div> {store.itemDisplayData.map(item => ( <div key={item.id}>{item.display}</div> ))} </div> ); } }

Cause 2: Component Depends on Too Much State

javascript
// Wrong: Component depends on too much state @observer class Dashboard extends React.Component { render() { return ( <div> <UserInfo /> <Settings /> <DataCount /> </div> ); } } // Correct: Split into multiple components @observer class UserInfo extends React.Component { render() { return ( <div> <div>{store.user.name}</div> <div>{store.user.email}</div> </div> ); } }

3. Memory Leaks

Problem Description

Reaction continues to run after component unmounts, causing memory leaks.

Solution

javascript
// Wrong: Forgot to clean up reaction useEffect(() => { autorun(() => { console.log(store.count); }); }, []); // Correct: Clean up reaction useEffect(() => { const dispose = autorun(() => { console.log(store.count); }); return () => { dispose(); // Clean up reaction }; }, []);

4. Async Operation Issues

Problem Description

State modifications in async operations don't take effect.

Solution

javascript
// Wrong: State modification in async operation @action async fetchData() { this.loading = true; const data = await fetch('/api/data').then(r => r.json()); this.data = data; // Not in action this.loading = false; // Not in action } // Correct: Use runInAction @action async fetchData() { this.loading = true; try { const data = await fetch('/api/data').then(r => r.json()); runInAction(() => { this.data = data; }); } finally { runInAction(() => { this.loading = false; }); } } // Or use flow fetchData = flow(function* () { this.loading = true; try { const response = yield fetch('/api/data'); const data = yield response.json(); this.data = data; } finally { this.loading = false; } });

5. computed Not Updating

Problem Description

computed property doesn't update as expected.

Common Causes and Solutions

Cause 1: 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); }

Cause 2: Dependencies Not Tracked Correctly

javascript
// Wrong: Dependencies not tracked correctly @computed get badComputed() { const data = this.data; // Not used in return value return this.items.length; } // Correct: Dependencies tracked correctly @computed get goodComputed() { return this.data.length + this.items.length; }

6. Circular Dependencies

Problem Description

Circular dependencies between stores cause infinite loops or performance issues.

Solution

javascript
// Wrong: 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; } } // Correct: 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; } }

7. Decorators Not Working

Problem Description

Errors occur or decorators don't take effect when using decorators.

Solution

Ensure Correct Configuration

javascript
// package.json { "babel": { "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }] ] } }

Or Use makeObservable

javascript
// Without decorators class Store { count = 0; constructor() { makeObservable(this, { count: observable }); } }

8. TypeScript Type Errors

Problem Description

Type errors occur when using TypeScript.

Solution

javascript
// Wrong: No type parameter class Store { count = 0; constructor() { makeObservable(this, { count: observable }); } } // Correct: Use type parameter class Store { count: number = 0; constructor() { makeObservable<Store>(this, { count: observable }); } }

9. Array Operation Issues

Problem Description

Array operations don't trigger updates.

Solution

javascript
// Wrong: Reassign entire array @action badAddItem(item) { this.items = [...this.items, item]; } // Correct: Use array methods @action goodAddItem(item) { this.items.push(item); } // Or use replace @action replaceItems(newItems) { this.items.replace(newItems); }

10. Difficult to Debug

Problem Description

Difficult to track state changes and dependencies.

Solution

Use trace

javascript
import { trace } from 'mobx'; // Trace computed trace(store.fullName); // Trace component rendering @observer class MyComponent extends React.Component { render() { trace(true); // Trace component rendering return <div>{store.count}</div>; } }

Use MobX DevTools

javascript
import { configure } from 'mobx'; configure({ // Enable debug mode useProxies: 'ifavailable', isolateGlobalState: true });

Best Practices Summary

  1. Always modify state in actions (MobX 6)
  2. Use observer to wrap components that need to respond to state changes
  3. Avoid creating new objects in render
  4. Use computed to optimize calculation logic
  5. Clean up reactions and side effects promptly
  6. Handle async operations correctly
  7. Avoid circular dependencies
  8. Use trace and DevTools for debugging
  9. Reasonably split components to reduce dependencies
  10. Follow MobX best practices

Following these best practices can avoid most common MobX issues and build stable, efficient applications.

标签:Mobx