异步操作 #

异步 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