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

在 MobX 中如何处理异步操作?

2月19日 17:56

在 MobX 中,处理异步操作需要特别注意,因为异步操作可能会在多个时刻修改状态。以下是处理异步操作的最佳实践:

1. 使用 action 包装异步操作

基本用法

javascript
import { 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 工具函数,可以更优雅地处理异步操作:

javascript
import { 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; }); } }

串行执行

javascript
fetchDataSequential = 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

javascript
class 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 组件中使用异步操作

javascript
import 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 中处理异步操作需要遵循以下原则:

  1. 始终在 action 中修改状态
  2. 使用 runInAction 或 flow 包装异步状态更新
  3. 正确处理加载状态和错误
  4. 取消不需要的请求
  5. 遵循最佳实践避免常见陷阱

正确处理异步操作是构建可靠 MobX 应用的关键。

标签:Mobx