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

如何在大型应用中使用 MobX 进行状态管理?

2月19日 17:49

在大型应用中,使用 MobX 进行状态管理需要考虑架构设计、模块化和可维护性。以下是构建大型 MobX 应用的最佳实践:

1. Store 架构设计

单一 Store vs 多个 Store

单一 Store

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

优点

  • 简单直接
  • 易于调试
  • 状态集中管理

缺点

  • 文件可能过大
  • 难以模块化
  • 团队协作困难

多个 Store

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 }); }; }

优点

  • 模块化清晰
  • 易于团队协作
  • 代码组织更好

缺点

  • 需要处理 store 间的依赖
  • 调试可能更复杂

2. Store 间通信

通过 RootStore 共享引用

javascript
class RootStore { constructor() { this.userStore = new UserStore(this); this.productStore = new ProductStore(this); this.cartStore = new CartStore(this); makeAutoObservable(this); } } // 在 CartStore 中访问 UserStore 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); }; }

使用依赖注入

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

使用事件总线

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. 状态持久化

使用 localStorage

javascript
class StorageStore { @observable state = {}; constructor(key, initialState = {}) { this.key = key; this.state = this.loadState() || initialState; makeAutoObservable(this); // 自动保存 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); } } } // 使用 const appStore = new StorageStore('appState', { user: null, theme: 'light', language: 'en' });

使用 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); } } }

使用 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. 代码组织

按功能模块组织

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

按类型组织

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. 类型安全(TypeScript)

定义 Store 接口

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; }; }

定义 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. 测试

测试 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); }); });

测试组件

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. 性能优化

使用 computed 缓存

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; } }

使用 reaction 延迟执行

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. 错误处理

全局错误处理

javascript
class ErrorStore { @observable errors = []; @action addError = (error) => { this.errors.push({ message: error.message, stack: error.stack, timestamp: Date.now() }); }; @action clearErrors = () => { this.errors = []; }; } // 在 RootStore 中集成 class RootStore { constructor() { this.errorStore = new ErrorStore(); this.userStore = new UserStore(this); this.productStore = new ProductStore(this); // 全局错误捕获 window.addEventListener('error', (event) => { this.errorStore.addError(event.error); }); window.addEventListener('unhandledrejection', (event) => { this.errorStore.addError(event.reason); }); } }

总结

构建大型 MobX 应用的关键点:

  1. 合理设计 Store 架构(单一 vs 多个)
  2. 处理 Store 间通信
  3. 实现状态持久化
  4. 良好的代码组织
  5. 使用 TypeScript 提高类型安全
  6. 编写全面的测试
  7. 优化性能
  8. 完善的错误处理

遵循这些最佳实践,可以构建可维护、可扩展的大型 MobX 应用。

标签:Mobx