03-状态管理与路由——06. Zustand 异步操作

📅 2026/6/16 9:09:09
03-状态管理与路由——06. Zustand 异步操作
06. Zustand 异步操作一、5W1H 概述维度内容What在 Zustand 中处理异步 actionWhy处理 API 请求、异步数据获取When需要从服务器获取数据或执行异步操作Wherestore 的异步 action 函数中Who需要处理异步数据的开发者HowfetchUser: async (id) { set({ loading: true }); ... }二、What - 什么是异步操作Zustand 中的异步操作是指在 action 中使用 async/await 处理 API 请求、定时器等异步任务。const useStore create((set) ({ data: null, loading: false, error: null, fetchData: async (url) { set({ loading: true }); try { const response await fetch(url); const data await response.json(); set({ data, loading: false }); } catch (error) { set({ error: error.message, loading: false }); } } }));三、Why - 为什么需要异步操作3.1 数据获取现代应用需要从服务器获取数据这是异步操作。3.2 用户体验通过 loading 状态提供视觉反馈提升用户体验。3.3 错误处理异步操作需要处理网络错误、超时等情况。四、When - 何时使用异步操作场景示例API 数据获取获取用户列表、商品详情表单提交注册、登录、发布内容文件上传上传图片、文档定时任务轮询数据、倒计时WebSocket实时消息、通知五、Where - 在哪里使用store 的 action 函数中组件中调用这些 action// store 中定义 const useUserStore create((set) ({ fetchUser: async (id) { ... } })); // 组件中调用 function UserProfile() { const fetchUser useUserStore(state state.fetchUser); useEffect(() { fetchUser(1); }, []); }六、Who - 谁需要使用所有需要处理异步数据API 请求的开发者。七、How - 如何处理异步操作7.1 基础异步操作import { create } from zustand; const useUserStore create((set) ({ user: null, loading: false, error: null, fetchUser: async (id) { set({ loading: true, error: null }); try { const response await fetch(/api/users/${id}); const user await response.json(); set({ user, loading: false }); } catch (error) { set({ error: error.message, loading: false }); } } })); // 组件中使用 function UserProfile({ userId }) { const { user, loading, error, fetchUser } useUserStore(); useEffect(() { fetchUser(userId); }, [userId, fetchUser]); if (loading) return div加载中.../div; if (error) return div错误: {error}/div; return div{user?.name}/div; }7.2 带请求取消的异步操作const useUserStore create((set) ({ user: null, loading: false, fetchUser: async (id) { const abortController new AbortController(); set({ loading: true }); try { const response await fetch(/api/users/${id}, { signal: abortController.signal }); const user await response.json(); set({ user, loading: false }); } catch (error) { if (error.name ! AbortError) { set({ error: error.message, loading: false }); } } return () abortController.abort(); } }));7.3 表单提交const useFormStore create((set) ({ isSubmitting: false, submitResult: null, error: null, submitForm: async (formData) { set({ isSubmitting: true, error: null }); try { const response await fetch(/api/submit, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(formData) }); const result await response.json(); if (!response.ok) { throw new Error(result.message || 提交失败); } set({ submitResult: result, isSubmitting: false }); return { success: true, data: result }; } catch (error) { set({ error: error.message, isSubmitting: false }); return { success: false, error: error.message }; } }, reset: () set({ isSubmitting: false, submitResult: null, error: null }) })); // 组件中使用 function ContactForm() { const { isSubmitting, error, submitResult, submitForm } useFormStore(); const handleSubmit async (e) { e.preventDefault(); const formData new FormData(e.target); const result await submitForm({ name: formData.get(name), email: formData.get(email) }); if (result.success) { e.target.reset(); } }; return ( form onSubmit{handleSubmit} input namename placeholder姓名 disabled{isSubmitting} / input nameemail typeemail placeholder邮箱 disabled{isSubmitting} / button typesubmit disabled{isSubmitting} {isSubmitting ? 提交中... : 提交} /button {error div classNameerror{error}/div} {submitResult div classNamesuccess提交成功/div} /form ); }7.4 分页数据加载const useProductStore create((set, get) ({ products: [], page: 1, hasMore: true, loading: false, loadMore: async () { const { page, loading, hasMore } get(); if (loading || !hasMore) return; set({ loading: true }); try { const response await fetch(/api/products?page${page}limit20); const data await response.json(); set((state) ({ products: [...state.products, ...data.items], page: state.page 1, hasMore: data.hasMore, loading: false })); } catch (error) { set({ error: error.message, loading: false }); } }, reset: () set({ products: [], page: 1, hasMore: true, loading: false }) }));7.5 并行请求const useDashboardStore create((set) ({ user: null, posts: null, comments: null, loading: false, error: null, fetchDashboardData: async (userId) { set({ loading: true, error: null }); try { // 并行请求 const [userRes, postsRes, commentsRes] await Promise.all([ fetch(/api/users/${userId}), fetch(/api/users/${userId}/posts), fetch(/api/users/${userId}/comments) ]); const [user, posts, comments] await Promise.all([ userRes.json(), postsRes.json(), commentsRes.json() ]); set({ user, posts, comments, loading: false }); } catch (error) { set({ error: error.message, loading: false }); } } }));7.6 依赖请求const useDataStore create((set, get) ({ user: null, userPosts: null, loading: false, fetchUser: async (userId) { set({ loading: true }); const response await fetch(/api/users/${userId}); const user await response.json(); set({ user, loading: false }); // 获取用户后自动获取其文章 const { fetchUserPosts } get(); await fetchUserPosts(user.id); }, fetchUserPosts: async (userId) { const response await fetch(/api/users/${userId}/posts); const posts await response.json(); set({ userPosts: posts }); } }));7.7 轮询数据const useNotificationStore create((set, get) ({ notifications: [], pollingInterval: null, startPolling: () { const interval setInterval(async () { const response await fetch(/api/notifications); const notifications await response.json(); set({ notifications }); }, 5000); set({ pollingInterval: interval }); }, stopPolling: () { const { pollingInterval } get(); if (pollingInterval) { clearInterval(pollingInterval); set({ pollingInterval: null }); } }, cleanup: () { const { stopPolling } get(); stopPolling(); } })); // 组件中使用 function Notifications() { const { notifications, startPolling, stopPolling, cleanup } useNotificationStore(); useEffect(() { startPolling(); return () cleanup(); }, []); return div{notifications.length} 条通知/div; }7.8 乐观更新const useTodoStore create((set, get) ({ todos: [], addTodo: async (text) { const tempId Date.now(); const newTodo { id: tempId, text, completed: false, isPending: true }; // 乐观更新 set((state) ({ todos: [...state.todos, newTodo] })); try { const response await fetch(/api/todos, { method: POST, body: JSON.stringify({ text }) }); const savedTodo await response.json(); // 替换临时 todo set((state) ({ todos: state.todos.map(todo todo.id tempId ? { ...savedTodo, isPending: false } : todo ) })); } catch (error) { // 回滚 set((state) ({ todos: state.todos.filter(todo todo.id ! tempId) })); } }, deleteTodo: async (id) { const originalTodos get().todos; // 乐观删除 set((state) ({ todos: state.todos.filter(todo todo.id ! id) })); try { await fetch(/api/todos/${id}, { method: DELETE }); } catch (error) { // 回滚 set({ todos: originalTodos }); } } }));八、错误处理最佳实践const useStore create((set) ({ data: null, loading: false, error: null, fetchData: async (url) { set({ loading: true, error: null }); try { const response await fetch(url); // 检查 HTTP 状态 if (!response.ok) { throw new Error(HTTP ${response.status}: ${response.statusText}); } const data await response.json(); set({ data, loading: false }); return { success: true, data }; } catch (error) { const errorMessage error.message || 网络错误请稍后重试; set({ error: errorMessage, loading: false }); return { success: false, error: errorMessage }; } }, clearError: () set({ error: null }) }));九、练习题实现一个用户数据获取的异步操作实现一个带 loading 状态的表单提交实现一个带分页加载的产品列表十、小结要点说明基础异步async/awaitset加载状态loading状态提供反馈错误处理try/catch捕获错误乐观更新先更新 UI失败后回滚请求取消AbortController