在 MobX 中,处理异步操作需要特别注意,因为异步操作可能会在多个时刻修改状态。以下是处理异步操作的最佳实践:
1. 使用 action 包装异步操作
基本用法
javascriptimport { observable, action, runInAction } from 'mobx'; class Store { @observable data = null; @observable loading = false; @observable error = null; @action async fetchData() { this.loading = true; this.error = null; try { const response = await fetch('/api/data'); const data = await response.json(); // 使用 runInAction 包装状态更新 runInAction(() => { this.data = data; }); } catch (error) { runInAction(() => { this.error = error.message; }); } finally { runInAction(() => { this.loading = false; }); } } }
为什么需要 runInAction
在异步操作中,状态更新可能不在 action 的执行上下文中。使用 runInAction 确保所有状态更新都在 action 中进行。
2. 使用 flow 处理异步操作
MobX 提供了 flow 工具函数,可以更优雅地处理异步操作:
javascriptimport { observable, flow } from 'mobx'; class Store { @observable data = null; @observable loading = false; @observable error = null; fetchData = flow(function* () { this.loading = true; this.error = null; try { const response = yield fetch('/api/data'); const data = yield response.json(); this.data = data; } catch (error) { this.error = error.message; } finally { this.loading = false; } }); }
flow 的优势
- 自动处理异步操作
- 代码更简洁,类似于同步代码
- 自动包装状态更新
- 更好的错误处理
3. 处理多个异步操作
并行执行
javascript@action async fetchMultipleData() { this.loading = true; try { const [users, posts] = await Promise.all([ fetch('/api/users').then(r => r.json()), fetch('/api/posts').then(r => r.json()) ]); runInAction(() => { this.users = users; this.posts = posts; }); } finally { runInAction(() => { this.loading = false; }); } }
串行执行
javascriptfetchDataSequential = flow(function* () { this.loading = true; try { const userResponse = yield fetch('/api/user'); const user = yield userResponse.json(); this.user = user; const postsResponse = yield fetch(`/api/users/${user.id}/posts`); const posts = yield postsResponse.json(); this.posts = posts; } finally { this.loading = false; } });
4. 取消异步操作
使用 AbortController
javascriptclass Store { @observable data = null; @observable loading = false; abortController = null; @action async fetchData() { // 取消之前的请求 if (this.abortController) { this.abortController.abort(); } this.abortController = new AbortController(); this.loading = true; try { const response = await fetch('/api/data', { signal: this.abortController.signal }); const data = await response.json(); runInAction(() => { this.data = data; }); } catch (error) { if (error.name !== 'AbortError') { runInAction(() => { this.error = error.message; }); } } finally { runInAction(() => { this.loading = false; }); } } @action cancelRequest() { if (this.abortController) { this.abortController.abort(); } } }
5. 错误处理和重试
基本错误处理
javascript@action async fetchDataWithRetry(maxRetries = 3) { this.loading = true; let retries = 0; while (retries < maxRetries) { try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.error = null; }); return; } catch (error) { retries++; if (retries >= maxRetries) { runInAction(() => { this.error = error.message; }); throw error; } // 等待一段时间后重试 await new Promise(resolve => setTimeout(resolve, 1000 * retries)); } finally { if (retries >= maxRetries) { runInAction(() => { this.loading = false; }); } } } }
6. 在 React 组件中使用异步操作
javascriptimport React, { useEffect } from 'react'; import { observer } from 'mobx-react-lite'; import { useLocalObservable } from 'mobx-react-lite'; const DataComponent = observer(() => { const store = useLocalObservable(() => ({ data: null, loading: false, error: null, fetchData: flow(function* () { this.loading = true; this.error = null; try { const response = yield fetch('/api/data'); const data = yield response.json(); this.data = data; } catch (error) { this.error = error.message; } finally { this.loading = false; } }) })); useEffect(() => { store.fetchData(); }, []); if (store.loading) return <div>Loading...</div>; if (store.error) return <div>Error: {store.error}</div>; return <div>{JSON.stringify(store.data)}</div>; });
7. 最佳实践总结
1. 始终在 action 中修改状态
- 使用
@action装饰器 - 使用
runInAction包装异步状态更新 - 使用
flow处理复杂的异步流程
2. 正确处理加载状态
- 在开始异步操作前设置 loading
- 在操作完成后清除 loading
- 在 finally 块中确保 loading 被清除
3. 处理错误
- 捕获所有可能的错误
- 将错误信息存储在 observable 中
- 在 UI 中显示错误信息
4. 取消不需要的请求
- 使用 AbortController 取消请求
- 在组件卸载时取消请求
- 避免内存泄漏
5. 使用 flow 简化代码
- 对于复杂的异步流程,优先使用 flow
- flow 使代码更易读和维护
- 自动处理 action 包装
8. 常见陷阱
1. 忘记使用 runInAction
javascript// 错误 @action async fetchData() { const data = await fetch('/api/data').then(r => r.json()); this.data = data; // 不在 action 中 } // 正确 @action async fetchData() { const data = await fetch('/api/data').then(r => r.json()); runInAction(() => { this.data = data; }); }
2. 在循环中创建多个 action
javascript// 错误 @action async fetchMultiple() { const items = await fetch('/api/items').then(r => r.json()); items.forEach(item => { runInAction(() => { // 多次调用 runInAction this.items.push(item); }); }); } // 正确 @action async fetchMultiple() { const items = await fetch('/api/items').then(r => r.json()); runInAction(() => { this.items.push(...items); }); }
3. 忘记清理副作用
javascript// 错误 useEffect(() => { store.fetchData(); // 组件卸载时请求可能仍在进行 }, []); // 正确 useEffect(() => { const task = store.fetchData(); return () => { task.cancel(); // 取消请求 }; }, []);
总结
在 MobX 中处理异步操作需要遵循以下原则:
- 始终在 action 中修改状态
- 使用 runInAction 或 flow 包装异步状态更新
- 正确处理加载状态和错误
- 取消不需要的请求
- 遵循最佳实践避免常见陷阱
正确处理异步操作是构建可靠 MobX 应用的关键。