在 MobX 中,异步操作需要特别注意,因为状态变化必须在 action 中进行。MobX 提供了多种方式来处理异步操作。
处理异步操作的方式
1. 使用 runInAction
javascriptimport { observable, action, runInAction } from 'mobx'; class UserStore { @observable users = []; @observable loading = false; @observable error = null; @action async fetchUsers() { this.loading = true; this.error = null; try { const response = await fetch('/api/users'); const data = await response.json(); runInAction(() => { this.users = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); } } }
2. 使用 async action
javascriptimport { observable, action } from 'mobx'; class UserStore { @observable users = []; @observable loading = false; @observable error = null; @action.bound async fetchUsers() { this.loading = true; this.error = null; try { const response = await fetch('/api/users'); const data = await response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } } }
3. 使用 flow
javascriptimport { observable, flow } from 'mobx'; class UserStore { @observable users = []; @observable loading = false; @observable error = null; fetchUsers = flow(function* () { this.loading = true; this.error = null; try { const response = yield fetch('/api/users'); const data = yield response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } }); }
详细对比
runInAction
优点:
- 灵活性高,可以在任何地方使用
- 适合处理复杂的异步逻辑
- 可以精确控制状态更新的时机
缺点:
- 需要手动包裹状态更新代码
- 代码结构可能不够清晰
适用场景:
- 需要在异步操作的不同阶段更新状态
- 复杂的异步逻辑
- 需要精确控制状态更新时机
javascript@action async complexOperation() { this.loading = true; try { const data1 = await this.fetchData1(); runInAction(() => { this.data1 = data1; }); const data2 = await this.fetchData2(data1.id); runInAction(() => { this.data2 = data2; }); const result = await this.processData(data1, data2); runInAction(() => { this.result = result; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); } }
async action
优点:
- 代码结构清晰
- 自动处理状态更新
- 不需要手动包裹代码
缺点:
- 灵活性较低
- 所有状态更新都在同一个 action 中
适用场景:
- 简单的异步操作
- 不需要精确控制状态更新时机
- 代码结构优先的场景
javascript@action.bound async simpleFetch() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); this.data = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } }
flow
优点:
- 代码结构最清晰
- 自动处理状态更新
- 支持取消操作
- 更好的错误处理
缺点:
- 需要学习 generator 语法
- 兼容性问题(需要 polyfill)
适用场景:
- 复杂的异步流程
- 需要取消操作的场景
- 需要更好的错误处理
javascriptfetchUsers = flow(function* () { this.loading = true; this.error = null; try { const response = yield fetch('/api/users'); const data = yield response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } }); // 可以取消 flow const fetchTask = store.fetchUsers(); fetchTask.cancel();
最佳实践
1. 统一使用 async action
javascriptclass ApiStore { @observable data = null; @observable loading = false; @observable error = null; @action.bound async fetchData(url) { this.loading = true; this.error = null; try { const response = await fetch(url); const data = await response.json(); this.data = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } } }
2. 使用 flow 处理复杂流程
javascriptclass OrderStore { @observable orders = []; @observable loading = false; @observable error = null; createOrder = flow(function* (orderData) { this.loading = true; this.error = null; try { // 验证订单 const validated = yield this.validateOrder(orderData); // 创建订单 const order = yield this.createOrderApi(validated); // 支付订单 const paid = yield this.payOrder(order.id); // 更新状态 this.orders.push(paid); this.loading = false; return paid; } catch (error) { this.error = error.message; this.loading = false; throw error; } }); }
3. 使用 runInAction 处理分步更新
javascriptclass UploadStore { @observable progress = 0; @observable files = []; @observable uploading = false; @action async uploadFiles(files) { this.uploading = true; this.progress = 0; try { for (let i = 0; i < files.length; i++) { const file = files[i]; await this.uploadFile(file); runInAction(() => { this.files.push(file); this.progress = ((i + 1) / files.length) * 100; }); } runInAction(() => { this.uploading = false; }); } catch (error) { runInAction(() => { this.uploading = false; }); throw error; } } }
4. 使用 reaction 处理自动加载
javascriptclass DataStore { @observable userId = null; @observable userData = null; @observable loading = false; constructor() { reaction( () => this.userId, (userId) => { if (userId) { this.loadUserData(userId); } } ); } @action.bound async loadUserData(userId) { this.loading = true; try { const response = await fetch(`/api/users/${userId}`); const data = await response.json(); this.userData = data; this.loading = false; } catch (error) { this.loading = false; } } }
5. 错误处理和重试
javascriptclass Store { @observable data = null; @observable loading = false; @observable error = null; @observable retryCount = 0; @action.bound async fetchDataWithRetry(url, maxRetries = 3) { this.loading = true; this.error = null; for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; this.retryCount = 0; }); return data; } catch (error) { runInAction(() => { this.retryCount = i + 1; }); if (i === maxRetries - 1) { runInAction(() => { this.error = error.message; this.loading = false; }); throw error; } // 等待后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } } }
常见错误
1. 在 async 函数中直接修改状态
javascript// ❌ 错误:在 async 函数中直接修改状态 @action async fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); this.data = data; // 不在 action 中 this.loading = false; } // ✅ 正确:使用 runInAction 或 async action @action async fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); } // 或者 @action.bound async fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); this.data = data; this.loading = false; }
2. 忘记处理错误
javascript// ❌ 错误:忘记处理错误 @action async fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); } // ✅ 正确:处理错误 @action async fetchData() { this.loading = true; this.error = null; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); } }
3. 忘记重置 loading 状态
javascript// ❌ 错误:忘记重置 loading 状态 @action async fetchData() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; }); } catch (error) { runInAction(() => { this.error = error.message; }); } } // ✅ 正确:在所有分支中重置 loading 状态 @action async fetchData() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); } }
性能优化
1. 使用 debounce 防抖
javascriptimport { debounce } from 'lodash'; class SearchStore { @observable query = ''; @observable results = []; @observable loading = false; constructor() { this.debouncedSearch = debounce(this.performSearch.bind(this), 300); } @action setQuery(query) { this.query = query; this.debouncedSearch(); } @action.bound async performSearch() { if (this.query.length < 2) { this.results = []; return; } this.loading = true; try { const response = await fetch(`/api/search?q=${this.query}`); const data = await response.json(); this.results = data; this.loading = false; } catch (error) { this.loading = false; } } }
2. 使用 requestAnimationFrame 优化 UI 更新
javascript@action async loadData() { this.loading = true; const data = await this.fetchData(); // 使用 requestAnimationFrame 优化 UI 更新 requestAnimationFrame(() => { runInAction(() => { this.data = data; this.loading = false; }); }); }
总结
- 使用 async action 处理简单的异步操作
- 使用 runInAction 处理需要精确控制状态更新时机的场景
- 使用 flow 处理复杂的异步流程
- 始终处理错误和重置 loading 状态
- 使用 reaction 处理自动加载
- 使用 debounce 防抖优化性能
- 使用 requestAnimationFrame 优化 UI 更新