异步操作 #
异步 Actions 基础 #
Zustand 完美支持异步操作。Actions 可以是 async 函数,内部可以执行任何异步逻辑。
基本异步 Action #
tsx
interface UserState {
user: User | null
isLoading: boolean
error: string | null
fetchUser: (id: string) => Promise<void>
}
const useUserStore = create<UserState>((set) => ({
user: null,
isLoading: false,
error: null,
fetchUser: async (id: string) => {
set({ isLoading: true, error: null })
try {
const response = await fetch(`/api/users/${id}`)
const user = await response.json()
set({ user, isLoading: false })
} catch (error) {
set({
error: error instanceof Error ? error.message : '获取用户失败',
isLoading: false
})
}
},
}))
在组件中使用 #
tsx
function UserProfile({ userId }: { userId: string }) {
const { user, isLoading, error, fetchUser } = useUserStore()
useEffect(() => {
fetchUser(userId)
}, [userId])
if (isLoading) return <div>加载中...</div>
if (error) return <div>错误: {error}</div>
if (!user) return <div>未找到用户</div>
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}
加载状态管理 #
单一加载状态 #
tsx
interface State {
data: Data | null
isLoading: boolean
error: string | null
fetchData: () => Promise<void>
}
const useDataStore = create<State>((set) => ({
data: null,
isLoading: false,
error: null,
fetchData: async () => {
set({ isLoading: true, error: null })
try {
const response = await fetch('/api/data')
const data = await response.json()
set({ data, isLoading: false })
} catch (error) {
set({
error: '获取数据失败',
isLoading: false
})
}
},
}))
多个加载状态 #
tsx
interface LoadingState {
isLoadingUser: boolean
isLoadingPosts: boolean
isLoadingComments: boolean
}
interface State extends LoadingState {
user: User | null
posts: Post[]
comments: Comment[]
fetchUser: (id: string) => Promise<void>
fetchPosts: (userId: string) => Promise<void>
fetchComments: (postId: string) => Promise<void>
}
const useStore = create<State>((set) => ({
isLoadingUser: false,
isLoadingPosts: false,
isLoadingComments: false,
user: null,
posts: [],
comments: [],
fetchUser: async (id) => {
set({ isLoadingUser: true })
try {
const response = await fetch(`/api/users/${id}`)
const user = await response.json()
set({ user, isLoadingUser: false })
} catch {
set({ isLoadingUser: false })
}
},
fetchPosts: async (userId) => {
set({ isLoadingPosts: true })
try {
const response = await fetch(`/api/posts?userId=${userId}`)
const posts = await response.json()
set({ posts, isLoadingPosts: false })
} catch {
set({ isLoadingPosts: false })
}
},
fetchComments: async (postId) => {
set({ isLoadingComments: true })
try {
const response = await fetch(`/api/comments?postId=${postId}`)
const comments = await response.json()
set({ comments, isLoadingComments: false })
} catch {
set({ isLoadingComments: false })
}
},
}))
使用加载状态映射 #
tsx
interface State {
loading: Record<string, boolean>
errors: Record<string, string | null>
data: Record<string, any>
fetch: (key: string, url: string) => Promise<void>
}
const useAsyncStore = create<State>((set) => ({
loading: {},
errors: {},
data: {},
fetch: async (key, url) => {
set((state) => ({
loading: { ...state.loading, [key]: true },
errors: { ...state.errors, [key]: null },
}))
try {
const response = await fetch(url)
const data = await response.json()
set((state) => ({
data: { ...state.data, [key]: data },
loading: { ...state.loading, [key]: false },
}))
} catch (error) {
set((state) => ({
errors: {
...state.errors,
[key]: error instanceof Error ? error.message : '请求失败'
},
loading: { ...state.loading, [key]: false },
}))
}
},
}))
// 使用
function Component() {
const isLoading = useAsyncStore((state) => state.loading['user-1'])
const user = useAsyncStore((state) => state.data['user-1'])
const fetch = useAsyncStore((state) => state.fetch)
useEffect(() => {
fetch('user-1', '/api/users/1')
}, [])
}
错误处理 #
基本错误处理 #
tsx
interface State {
data: Data | null
error: string | null
fetchData: () => Promise<void>
clearError: () => void
}
const useStore = create<State>((set) => ({
data: null,
error: null,
fetchData: async () => {
try {
const response = await fetch('/api/data')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
set({ data, error: null })
} catch (error) {
const message = error instanceof Error ? error.message : '未知错误'
set({ error: message })
}
},
clearError: () => set({ error: null }),
}))
结构化错误处理 #
tsx
interface ApiError {
code: string
message: string
details?: Record<string, string[]>
}
interface State {
data: Data | null
error: ApiError | null
fetchData: () => Promise<void>
}
const useStore = create<State>((set) => ({
data: null,
error: null,
fetchData: async () => {
try {
const response = await fetch('/api/data')
if (!response.ok) {
const errorData = await response.json()
throw {
code: errorData.code || 'UNKNOWN_ERROR',
message: errorData.message || '请求失败',
details: errorData.details,
}
}
const data = await response.json()
set({ data, error: null })
} catch (error) {
set({ error: error as ApiError })
}
},
}))
并发控制 #
防止重复请求 #
tsx
interface State {
isFetching: boolean
data: Data | null
fetchData: () => Promise<void>
}
const useStore = create<State>((set, get) => ({
isFetching: false,
data: null,
fetchData: async () => {
// 如果正在请求,直接返回
if (get().isFetching) return
set({ isFetching: true })
try {
const response = await fetch('/api/data')
const data = await response.json()
set({ data, isFetching: false })
} catch {
set({ isFetching: false })
}
},
}))
请求取消 #
tsx
interface State {
data: Data | null
abortController: AbortController | null
fetchData: () => Promise<void>
cancelFetch: () => void
}
const useStore = create<State>((set, get) => ({
data: null,
abortController: null,
fetchData: async () => {
// 取消之前的请求
get().abortController?.abort()
const controller = new AbortController()
set({ abortController: controller })
try {
const response = await fetch('/api/data', {
signal: controller.signal,
})
const data = await response.json()
set({ data, abortController: null })
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
console.log('请求已取消')
}
set({ abortController: null })
}
},
cancelFetch: () => {
get().abortController?.abort()
set({ abortController: null })
},
}))
请求队列 #
tsx
interface QueueItem {
id: string
execute: () => Promise<any>
}
interface State {
queue: QueueItem[]
isProcessing: boolean
addToQueue: (item: QueueItem) => void
processQueue: () => Promise<void>
}
const useQueueStore = create<State>((set, get) => ({
queue: [],
isProcessing: false,
addToQueue: (item) => {
set((state) => ({
queue: [...state.queue, item]
}))
if (!get().isProcessing) {
get().processQueue()
}
},
processQueue: async () => {
const { queue } = get()
if (queue.length === 0) {
set({ isProcessing: false })
return
}
set({ isProcessing: true })
const [first, ...rest] = queue
set({ queue: rest })
try {
await first.execute()
} catch (error) {
console.error('Queue item failed:', error)
}
// 处理下一个
get().processQueue()
},
}))
数据缓存 #
简单缓存 #
tsx
interface State {
cache: Record<string, { data: any; timestamp: number }>
cacheTime: number
fetchWithCache: (key: string, url: string) => Promise<any>
}
const useCacheStore = create<State>((set, get) => ({
cache: {},
cacheTime: 5 * 60 * 1000, // 5分钟
fetchWithCache: async (key, url) => {
const { cache, cacheTime } = get()
const cached = cache[key]
// 检查缓存是否有效
if (cached && Date.now() - cached.timestamp < cacheTime) {
return cached.data
}
const response = await fetch(url)
const data = await response.json()
set((state) => ({
cache: {
...state.cache,
[key]: { data, timestamp: Date.now() }
}
}))
return data
},
}))
SWR (Stale-While-Revalidate) #
tsx
interface State {
data: Record<string, any>
isValidating: Record<string, boolean>
swr: <T>(key: string, fetcher: () => Promise<T>) => T | undefined
}
const useSWRStore = create<State>((set, get) => ({
data: {},
isValidating: {},
swr: (key, fetcher) => {
const { data, isValidating } = get()
// 返回缓存数据
if (data[key]) {
// 后台重新验证
if (!isValidating[key]) {
set((state) => ({
isValidating: { ...state.isValidating, [key]: true }
}))
fetcher().then((newData) => {
set((state) => ({
data: { ...state.data, [key]: newData },
isValidating: { ...state.isValidating, [key]: false }
}))
})
}
return data[key]
}
// 首次请求
if (!isValidating[key]) {
set((state) => ({
isValidating: { ...state.isValidating, [key]: true }
}))
fetcher().then((newData) => {
set((state) => ({
data: { ...state.data, [key]: newData },
isValidating: { ...state.isValidating, [key]: false }
}))
})
}
return undefined
},
}))
实际案例 #
完整的 API Store #
tsx
interface User {
id: string
name: string
email: string
}
interface UserState {
users: User[]
currentUser: User | null
isLoading: boolean
error: string | null
fetchUsers: () => Promise<void>
fetchUser: (id: string) => Promise<void>
createUser: (user: Omit<User, 'id'>) => Promise<User>
updateUser: (id: string, user: Partial<User>) => Promise<void>
deleteUser: (id: string) => Promise<void>
clearError: () => void
}
const useUserStore = create<UserState>((set, get) => ({
users: [],
currentUser: null,
isLoading: false,
error: null,
fetchUsers: async () => {
set({ isLoading: true, error: null })
try {
const response = await fetch('/api/users')
if (!response.ok) throw new Error('获取用户列表失败')
const users = await response.json()
set({ users, isLoading: false })
} catch (error) {
set({
error: error instanceof Error ? error.message : '未知错误',
isLoading: false
})
}
},
fetchUser: async (id) => {
set({ isLoading: true, error: null })
try {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) throw new Error('获取用户失败')
const user = await response.json()
set({ currentUser: user, isLoading: false })
} catch (error) {
set({
error: error instanceof Error ? error.message : '未知错误',
isLoading: false
})
}
},
createUser: async (userData) => {
set({ isLoading: true, error: null })
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData),
})
if (!response.ok) throw new Error('创建用户失败')
const user = await response.json()
set((state) => ({
users: [...state.users, user],
isLoading: false,
}))
return user
} catch (error) {
set({
error: error instanceof Error ? error.message : '未知错误',
isLoading: false
})
throw error
}
},
updateUser: async (id, userData) => {
set({ isLoading: true, error: null })
try {
const response = await fetch(`/api/users/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData),
})
if (!response.ok) throw new Error('更新用户失败')
const updatedUser = await response.json()
set((state) => ({
users: state.users.map((u) => (u.id === id ? updatedUser : u)),
currentUser: state.currentUser?.id === id ? updatedUser : state.currentUser,
isLoading: false,
}))
} catch (error) {
set({
error: error instanceof Error ? error.message : '未知错误',
isLoading: false
})
}
},
deleteUser: async (id) => {
set({ isLoading: true, error: null })
try {
const response = await fetch(`/api/users/${id}`, {
method: 'DELETE',
})
if (!response.ok) throw new Error('删除用户失败')
set((state) => ({
users: state.users.filter((u) => u.id !== id),
currentUser: state.currentUser?.id === id ? null : state.currentUser,
isLoading: false,
}))
} catch (error) {
set({
error: error instanceof Error ? error.message : '未知错误',
isLoading: false
})
}
},
clearError: () => set({ error: null }),
}))
最佳实践 #
1. 分离加载和错误状态 #
tsx
// ✅ 好:独立的状态
interface State {
data: Data | null
isLoading: boolean
error: string | null
}
// ❌ 不好:混合状态
interface State {
status: 'idle' | 'loading' | 'error' | 'success'
data: Data | null
error: string | null
}
2. 使用 TypeScript 类型 #
tsx
// ✅ 好:完整类型定义
interface FetchResult<T> {
data: T | null
error: string | null
}
const fetchData = async (): Promise<FetchResult<User>> => {
// ...
}
3. 错误边界处理 #
tsx
// 在组件中使用错误边界
function UserList() {
const { users, error, fetchUsers } = useUserStore()
if (error) {
return (
<div>
<p>错误: {error}</p>
<button onClick={fetchUsers}>重试</button>
</div>
)
}
// ...
}
总结 #
处理异步操作的关键点:
- 使用 async/await 定义异步 Actions
- 管理好加载和错误状态
- 实现请求取消和防重复
- 使用缓存优化性能
- 处理好并发请求
接下来,让我们学习 中间件系统,扩展 Zustand 的功能。
最后更新:2026-03-28