异步逻辑与 API 请求
在现代前端应用中,异步操作(如 API 请求)是必不可少的。Zustand 提供了简洁而强大的方式来处理异步逻辑,让你能够轻松管理异步状态。
基本异步 Actions
在 Zustand 中,你可以直接在 Store 中定义异步 Actions。这些 Actions 通常是返回 Promise 的函数。
定义异步 Action
javascript
import { create } from 'zustand'
const useUserStore = create((set) => ({
user: null,
isLoading: false,
error: null,
// 异步 Action:获取用户信息
fetchUser: async (userId) => {
try {
set({ isLoading: true, error: null })
const response = await fetch(`https://api.example.com/users/${userId}`)
if (!response.ok) {
throw new Error('Failed to fetch user')
}
const user = await response.json()
set({ user, isLoading: false })
} catch (error) {
set({ error: error.message, isLoading: false })
}
},
}))使用异步 Action
jsx
function UserProfile({ userId }) {
const { user, isLoading, error, fetchUser } = useUserStore()
// 组件挂载时获取用户信息
useEffect(() => {
fetchUser(userId)
}, [userId, fetchUser])
if (isLoading) {
return <div>Loading...</div>
}
if (error) {
return <div>Error: {error}</div>
}
if (!user) {
return <div>No user found</div>
}
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
{/* 其他用户信息 */}
</div>
)
}异步状态管理最佳实践
1. 总是管理加载状态
在异步操作期间,向用户显示加载指示器是一种良好的用户体验。
javascript
const useStore = create((set) => ({
data: null,
isLoading: false, // 加载状态
error: null,
fetchData: async () => {
set({ isLoading: true, error: null }) // 开始加载
try {
const data = await fetchDataFromAPI()
set({ data, isLoading: false }) // 加载完成
} catch (error) {
set({ error: error.message, isLoading: false }) // 加载失败
}
},
}))2. 妥善处理错误
确保捕获异步操作中的错误,并向用户提供有意义的反馈。
javascript
const useStore = create((set) => ({
data: null,
isLoading: false,
error: null, // 错误状态
fetchData: async () => {
set({ isLoading: true, error: null })
try {
const data = await fetchDataFromAPI()
set({ data, isLoading: false })
} catch (error) {
set({ error: error.message, isLoading: false }) // 设置错误信息
}
},
}))3. 使用 get 获取当前状态
在异步 Action 中,你可以使用 get 参数获取当前状态。
javascript
const useStore = create((set, get) => ({
todos: [],
// 异步 Action:添加任务
addTodo: async (text) => {
try {
const newTodo = await createTodoAPI({ text })
set((state) => ({
todos: [...state.todos, newTodo]
}))
} catch (error) {
console.error('Failed to add todo:', error)
}
},
// 异步 Action:更新任务状态
toggleTodo: async (id) => {
try {
// 使用 get 获取当前任务
const todo = get().todos.find((todo) => todo.id === id)
if (!todo) return
const updatedTodo = await updateTodoAPI(id, {
completed: !todo.completed
})
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? updatedTodo : todo
)
}))
} catch (error) {
console.error('Failed to toggle todo:', error)
}
},
}))高级异步模式
1. 并行请求
你可以使用 Promise.all 来处理并行的异步请求。
javascript
const useStore = create((set) => ({
users: [],
posts: [],
isLoading: false,
error: null,
// 并行获取用户和帖子
fetchData: async () => {
set({ isLoading: true, error: null })
try {
const [users, posts] = await Promise.all([
fetchUsersAPI(),
fetchPostsAPI()
])
set({ users, posts, isLoading: false })
} catch (error) {
set({ error: error.message, isLoading: false })
}
},
}))2. 串行请求
对于需要按顺序执行的异步请求,你可以使用 await 链式调用。
javascript
const useStore = create((set) => ({
user: null,
userPosts: [],
isLoading: false,
error: null,
// 先获取用户,再获取用户的帖子
fetchUserWithPosts: async (userId) => {
set({ isLoading: true, error: null })
try {
const user = await fetchUserAPI(userId)
const userPosts = await fetchUserPostsAPI(userId)
set({ user, userPosts, isLoading: false })
} catch (error) {
set({ error: error.message, isLoading: false })
}
},
}))3. 取消请求
对于可能被中断的异步请求,你可以使用 AbortController 来取消请求。
javascript
const useStore = create((set) => {
let abortController = null
return {
data: null,
isLoading: false,
error: null,
fetchData: async () => {
// 取消之前的请求
if (abortController) {
abortController.abort()
}
// 创建新的 AbortController
abortController = new AbortController()
set({ isLoading: true, error: null })
try {
const response = await fetch('https://api.example.com/data', {
signal: abortController.signal // 关联 AbortController
})
const data = await response.json()
set({ data, isLoading: false })
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request aborted')
return
}
set({ error: error.message, isLoading: false })
}
},
// 取消请求的 Action
cancelFetch: () => {
if (abortController) {
abortController.abort()
set({ isLoading: false })
}
},
}
})与其他库结合使用
与 Axios 结合
Axios 是一个流行的 HTTP 客户端库,你可以轻松地将它与 Zustand 结合使用。
javascript
import axios from 'axios'
import { create } from 'zustand'
const usePostStore = create((set) => ({
posts: [],
isLoading: false,
error: null,
fetchPosts: async () => {
set({ isLoading: true, error: null })
try {
const response = await axios.get('https://api.example.com/posts')
set({ posts: response.data, isLoading: false })
} catch (error) {
set({ error: error.message, isLoading: false })
}
},
createPost: async (postData) => {
set({ isLoading: true, error: null })
try {
const response = await axios.post('https://api.example.com/posts', postData)
set((state) => ({
posts: [...state.posts, response.data],
isLoading: false
}))
} catch (error) {
set({ error: error.message, isLoading: false })
}
},
}))与 TanStack Query 结合
TanStack Query(原 React Query)是一个强大的数据获取库,它可以与 Zustand 很好地结合使用。Zustand 管理应用状态,而 TanStack Query 管理服务器状态。
javascript
// Zustand Store:管理应用状态
const useAppStore = create((set) => ({
isLoggedIn: false,
userPreferences: {},
login: (user) => set({ isLoggedIn: true }),
logout: () => set({ isLoggedIn: false }),
updatePreferences: (preferences) => set({ userPreferences: preferences }),
}))
// TanStack Query:管理服务器状态
function PostsList() {
const { data: posts, isLoading, error } = useQuery({
queryKey: ['posts'],
queryFn: () => fetch('https://api.example.com/posts').then(res => res.json())
})
// 使用 Zustand 的状态
const isLoggedIn = useAppStore(state => state.isLoggedIn)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
<h1>Posts</h1>
{posts.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
{isLoggedIn && <button>Edit Post</button>}
</div>
))}
</div>
)
}最佳实践总结
- 总是管理加载状态和错误状态:提供良好的用户体验
- 使用不可变更新:确保状态更新的可预测性
- 避免在组件中处理复杂的异步逻辑:将异步逻辑封装在 Store 中
- 考虑使用专业的数据获取库:如 TanStack Query,处理缓存、重新获取等高级功能
- 取消不必要的请求:避免资源浪费和潜在的错误
在接下来的章节中,我们将学习 中间件:Persist 持久化,了解如何在 Zustand 中持久化存储状态。