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

How to use MobX for state management in large applications?

2月19日 17:49

In large applications, using MobX for state management requires consideration of architecture design, modularity, and maintainability. Here are best practices for building large-scale MobX applications:

1. Store Architecture Design

Single Store vs Multiple Stores

Single Store

javascript
class RootStore { @observable user = null; @observable products = []; @observable cart = []; @observable orders = []; constructor() { makeAutoObservable(this); } }

Pros:

  • Simple and direct
  • Easy to debug
  • Centralized state management

Cons:

  • File may become too large
  • Difficult to modularize
  • Hard for team collaboration

Multiple Stores

javascript
class UserStore { @observable user = null; @observable isAuthenticated = false; constructor(rootStore) { this.rootStore = rootStore; makeAutoObservable(this); } @action login = async (credentials) => { const user = await api.login(credentials); this.user = user; this.isAuthenticated = true; }; } class ProductStore { @observable products = []; @observable loading = false; constructor(rootStore) { this.rootStore = rootStore; makeAutoObservable(this); } @computed get featuredProducts() { return this.products.filter(p => p.featured); } } class CartStore { @observable items = []; constructor(rootStore) { this.rootStore = rootStore; makeAutoObservable(this); } @computed get total() { return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0); } @action addItem = (product) => { this.items.push({ ...product, quantity: 1 }); }; }

Pros:

  • Clear modularity
  • Easy for team collaboration
  • Better code organization

Cons:

  • Need to handle dependencies between stores
  • Debugging may be more complex

2. Store Communication

Sharing References Through RootStore

javascript
class RootStore { constructor() { this.userStore = new UserStore(this); this.productStore = new ProductStore(this); this.cartStore = new CartStore(this); makeAutoObservable(this); } } // Access UserStore in CartStore class CartStore { @action checkout = async () => { const user = this.rootStore.userStore.user; if (!user) { throw new Error('User not logged in'); } await api.createOrder(this.items, user.id); }; }

Using Dependency Injection

javascript
class CartStore { constructor(userStore) { this.userStore = userStore; makeAutoObservable(this); } @action checkout = async () => { const user = this.userStore.user; // ... }; } // Create store const userStore = new UserStore(); const cartStore = new CartStore(userStore);

Using Event Bus

javascript
class EventBus { @observable events = []; emit(event, data) { this.events.push({ event, data, timestamp: Date.now() }); } on(event, callback) { return reaction( () => this.events.filter(e => e.event === event), (events) => { if (events.length > 0) { callback(events[events.length - 1].data); } } ); } }

3. State Persistence

Using localStorage

javascript
class StorageStore { @observable state = {}; constructor(key, initialState = {}) { this.key = key; this.state = this.loadState() || initialState; makeAutoObservable(this); // Auto save autorun(() => { this.saveState(toJS(this.state)); }); } loadState() { try { const saved = localStorage.getItem(this.key); return saved ? JSON.parse(saved) : null; } catch (error) { console.error('Failed to load state:', error); return null; } } saveState(state) { try { localStorage.setItem(this.key, JSON.stringify(state)); } catch (error) { console.error('Failed to save state:', error); } } } // Usage const appStore = new StorageStore('appState', { user: null, theme: 'light', language: 'en' });

Using sessionStorage

javascript
class SessionStorageStore extends StorageStore { loadState() { try { const saved = sessionStorage.getItem(this.key); return saved ? JSON.parse(saved) : null; } catch (error) { return null; } } saveState(state) { try { sessionStorage.setItem(this.key, JSON.stringify(state)); } catch (error) { console.error('Failed to save state:', error); } } }

Using IndexedDB

javascript
class IndexedDBStore { @observable data = null; constructor(dbName, storeName) { this.dbName = dbName; this.storeName = storeName; makeAutoObservable(this); this.initDB(); } async initDB() { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, 1); request.onerror = () => reject(request.error); request.onsuccess = () => { this.db = request.result; this.loadData(); resolve(); }; request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains(this.storeName)) { db.createObjectStore(this.storeName); } }; }); } async loadData() { const transaction = this.db.transaction([this.storeName], 'readonly'); const store = transaction.objectStore(this.storeName); const request = store.get('data'); request.onsuccess = () => { runInAction(() => { this.data = request.result; }); }; } async saveData(data) { const transaction = this.db.transaction([this.storeName], 'readwrite'); const store = transaction.objectStore(this.storeName); store.put(data, 'data'); } }

4. Code Organization

Organize by Feature Module

shell
src/ stores/ user/ index.js actions.js getters.js product/ index.js actions.js getters.js cart/ index.js actions.js getters.js index.js

Organize by Type

shell
src/ stores/ observables/ user.js products.js cart.js actions/ userActions.js productActions.js cartActions.js computed/ userComputed.js productComputed.js cartComputed.js index.js

5. Type Safety (TypeScript)

Define Store Interface

typescript
interface IUserStore { user: User | null; isAuthenticated: boolean; login: (credentials: Credentials) => Promise<void>; logout: () => void; } class UserStore implements IUserStore { @observable user: User | null = null; @observable isAuthenticated: boolean = false; constructor(private rootStore: RootStore) { makeAutoObservable(this); } @action login = async (credentials: Credentials): Promise<void> => { const user = await api.login(credentials); this.user = user; this.isAuthenticated = true; }; @action logout = (): void => { this.user = null; this.isAuthenticated = false; }; }

Define RootStore

typescript
interface IRootStore { userStore: IUserStore; productStore: IProductStore; cartStore: ICartStore; } class RootStore implements IRootStore { userStore: IUserStore; productStore: IProductStore; cartStore: ICartStore; constructor() { this.userStore = new UserStore(this); this.productStore = new ProductStore(this); this.cartStore = new CartStore(this); makeAutoObservable(this); } }

6. Testing

Test Store

javascript
import { UserStore } from './UserStore'; describe('UserStore', () => { let store; beforeEach(() => { store = new UserStore(); }); it('should initialize with default values', () => { expect(store.user).toBeNull(); expect(store.isAuthenticated).toBe(false); }); it('should login user', async () => { await store.login({ username: 'test', password: 'test' }); expect(store.user).not.toBeNull(); expect(store.isAuthenticated).toBe(true); }); it('should logout user', () => { store.user = { id: 1, name: 'Test' }; store.isAuthenticated = true; store.logout(); expect(store.user).toBeNull(); expect(store.isAuthenticated).toBe(false); }); });

Test Component

javascript
import { render, screen } from '@testing-library/react'; import { observer } from 'mobx-react-lite'; import { UserStore } from './UserStore'; const TestComponent = observer(({ store }) => ( <div> {store.isAuthenticated ? ( <div>Welcome, {store.user?.name}</div> ) : ( <div>Please login</div> )} </div> )); describe('TestComponent', () => { it('should show login message when not authenticated', () => { const store = new UserStore(); render(<TestComponent store={store} />); expect(screen.getByText('Please login')).toBeInTheDocument(); }); it('should show welcome message when authenticated', () => { const store = new UserStore(); store.user = { id: 1, name: 'Test' }; store.isAuthenticated = true; render(<TestComponent store={store} />); expect(screen.getByText('Welcome, Test')).toBeInTheDocument(); }); });

7. Performance Optimization

Use computed Caching

javascript
class ProductStore { @observable products = []; @observable filters = { category: null, priceRange: null, search: '' }; @computed get filteredProducts() { let result = this.products; if (this.filters.category) { result = result.filter(p => p.category === this.filters.category); } if (this.filters.priceRange) { result = result.filter(p => p.price >= this.filters.priceRange.min && p.price <= this.filters.priceRange.max ); } if (this.filters.search) { const search = this.filters.search.toLowerCase(); result = result.filter(p => p.name.toLowerCase().includes(search) ); } return result; } }

Use reaction for Delayed Execution

javascript
class SearchStore { @observable query = ''; @observable results = []; @observable loading = false; constructor() { makeAutoObservable(this); reaction( () => this.query, (query) => { this.performSearch(query); }, { delay: 300 } ); } @action performSearch = async (query) => { if (!query) { this.results = []; return; } this.loading = true; try { this.results = await api.search(query); } finally { this.loading = false; } }; }

8. Error Handling

Global Error Handling

javascript
class ErrorStore { @observable errors = []; @action addError = (error) => { this.errors.push({ message: error.message, stack: error.stack, timestamp: Date.now() }); }; @action clearErrors = () => { this.errors = []; }; } // Integrate in RootStore class RootStore { constructor() { this.errorStore = new ErrorStore(); this.userStore = new UserStore(this); this.productStore = new ProductStore(this); // Global error capture window.addEventListener('error', (event) => { this.errorStore.addError(event.error); }); window.addEventListener('unhandledrejection', (event) => { this.errorStore.addError(event.reason); }); } }

Summary

Key points for building large-scale MobX applications:

  1. Reasonable Store architecture design (single vs multiple)
  2. Handle store communication
  3. Implement state persistence
  4. Good code organization
  5. Use TypeScript for type safety
  6. Write comprehensive tests
  7. Optimize performance
  8. Complete error handling

Following these best practices can build maintainable, scalable large-scale MobX applications.

标签:Mobx