Actions 与状态更新 #

什么是 Actions? #

在 Zustand 中,Actions 是用于更新状态的函数。与 Redux 不同,Zustand 的 Actions 直接定义在 store 中,无需额外的 action creators 或 reducers。

基本结构 #

tsx
interface StoreState {
  // 状态
  count: number
  
  // Actions
  increment: () => void
  decrement: () => void
  reset: () => void
}

const useStore = create<StoreState>((set) => ({
  count: 0,
  
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}))

定义 Actions #

简单 Actions #

tsx
const useStore = create((set) => ({
  name: '',
  
  // 直接设置
  setName: (name: string) => set({ name }),
  
  // 清空
  clearName: () => set({ name: '' }),
}))

带参数的 Actions #

tsx
interface Todo {
  id: string
  text: string
  completed: boolean
}

const useTodoStore = create<TodoState>((set) => ({
  todos: [],
  
  // 添加 todo
  addTodo: (text: string) => set((state) => ({
    todos: [
      ...state.todos,
      { id: Date.now().toString(), text, completed: false }
    ]
  })),
  
  // 切换完成状态
  toggleTodo: (id: string) => set((state) => ({
    todos: state.todos.map((todo) =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    )
  })),
  
  // 删除 todo
  removeTodo: (id: string) => set((state) => ({
    todos: state.todos.filter((todo) => todo.id !== id)
  })),
}))

使用 get 函数 #

tsx
const useStore = create((set, get) => ({
  count: 0,
  
  increment: () => set((state) => ({ count: state.count + 1 })),
  
  // 使用 get 获取当前状态
  doubleAndIncrement: () => {
    const currentCount = get().count
    set({ count: currentCount * 2 + 1 })
  },
  
  // 异步操作中使用 get
  asyncIncrement: async () => {
    const currentCount = get().count
    await new Promise((resolve) => setTimeout(resolve, 1000))
    set({ count: currentCount + 1 })
  },
}))

不可变更新 #

Zustand 遵循 React 的不可变数据原则。更新状态时,必须创建新的对象/数组,而不是直接修改。

对象更新 #

tsx
interface User {
  name: string
  email: string
  settings: {
    theme: 'light' | 'dark'
    notifications: boolean
  }
}

const useUserStore = create<{ user: User; updateUser: (data: Partial<User>) => void }>((set) => ({
  user: {
    name: 'John',
    email: 'john@example.com',
    settings: {
      theme: 'light',
      notifications: true,
    },
  },
  
  // 更新顶层属性
  updateUser: (data) => set((state) => ({
    user: { ...state.user, ...data }
  })),
  
  // 更新嵌套属性
  updateTheme: (theme) => set((state) => ({
    user: {
      ...state.user,
      settings: {
        ...state.user.settings,
        theme,
      },
    },
  })),
}))

数组更新 #

tsx
interface Item {
  id: string
  name: string
}

const useListStore = create((set) => ({
  items: [] as Item[],
  
  // 添加到末尾
  addItem: (item: Item) => set((state) => ({
    items: [...state.items, item]
  })),
  
  // 添加到开头
  prependItem: (item: Item) => set((state) => ({
    items: [item, ...state.items]
  })),
  
  // 删除
  removeItem: (id: string) => set((state) => ({
    items: state.items.filter((item) => item.id !== id)
  })),
  
  // 更新
  updateItem: (id: string, data: Partial<Item>) => set((state) => ({
    items: state.items.map((item) =>
      item.id === id ? { ...item, ...data } : item
    )
  })),
  
  // 排序
  sortItems: () => set((state) => ({
    items: [...state.items].sort((a, b) => a.name.localeCompare(b.name))
  })),
  
  // 反转
  reverseItems: () => set((state) => ({
    items: [...state.items].reverse()
  })),
}))

使用 Immer 简化更新 #

对于复杂的嵌套状态,可以使用 Immer 中间件:

tsx
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

const useStore = create(
  immer((set) => ({
    user: {
      name: 'John',
      settings: {
        theme: 'light',
        notifications: {
          email: true,
          push: false,
        },
      },
    },
    
    // 使用 Immer,可以直接"修改"状态
    updateTheme: (theme: 'light' | 'dark') => set((state) => {
      state.user.settings.theme = theme
    }),
    
    updateNotification: (type: 'email' | 'push', value: boolean) => set((state) => {
      state.user.settings.notifications[type] = value
    }),
  }))
)

批量更新 #

单次更新多个状态 #

tsx
const useStore = create((set) => ({
  count: 0,
  name: '',
  isLoading: false,
  
  // 批量更新(只触发一次重渲染)
  initialize: () => set({
    count: 0,
    name: 'Initial',
    isLoading: false,
  }),
  
  // 基于前一个状态批量更新
  reset: () => set((state) => ({
    count: 0,
    name: '',
    isLoading: false,
  })),
}))

批量操作 #

tsx
const useCartStore = create((set) => ({
  items: [] as CartItem[],
  total: 0,
  count: 0,
  
  // 添加多个商品(一次更新)
  addMultipleItems: (newItems: CartItem[]) => set((state) => {
    const items = [...state.items, ...newItems]
    return {
      items,
      total: items.reduce((sum, item) => sum + item.price, 0),
      count: items.length,
    }
  }),
}))

复杂状态操作 #

条件更新 #

tsx
const useCounterStore = create((set, get) => ({
  count: 0,
  max: 100,
  min: 0,
  
  increment: () => {
    const { count, max } = get()
    if (count < max) {
      set({ count: count + 1 })
    }
  },
  
  decrement: () => {
    const { count, min } = get()
    if (count > min) {
      set({ count: count - 1 })
    }
  },
  
  setCount: (value: number) => {
    const { max, min } = get()
    const clampedValue = Math.min(max, Math.max(min, value))
    set({ count: clampedValue })
  },
}))

链式更新 #

tsx
const useGameStore = create((set, get) => ({
  score: 0,
  level: 1,
  lives: 3,
  
  addScore: (points: number) => {
    const { score, level } = get()
    const newScore = score + points
    
    // 检查是否升级
    if (newScore >= level * 100) {
      set({ score: newScore, level: level + 1 })
    } else {
      set({ score: newScore })
    }
  },
  
  loseLife: () => {
    const { lives } = get()
    if (lives <= 1) {
      // 游戏结束
      set({ lives: 0, score: 0, level: 1 })
    } else {
      set({ lives: lives - 1 })
    }
  },
}))

事务性更新 #

tsx
const useTransferStore = create((set, get) => ({
  accounts: {
    alice: 1000,
    bob: 500,
  },
  
  transfer: (from: string, to: string, amount: number) => {
    const { accounts } = get()
    
    // 验证
    if (accounts[from] < amount) {
      throw new Error('Insufficient funds')
    }
    
    // 原子更新
    set({
      accounts: {
        ...accounts,
        [from]: accounts[from] - amount,
        [to]: (accounts[to] || 0) + amount,
      },
    })
  },
}))

Action 模式 #

Action 分离模式 #

tsx
// 定义状态
interface State {
  count: number
}

// 定义 Actions
interface Actions {
  increment: () => void
  decrement: () => void
  reset: () => void
}

type StoreState = State & Actions

const initialState: State = {
  count: 0,
}

const createActions = (set: any, get: any): Actions => ({
  increment: () => set((state: State) => ({ count: state.count + 1 })),
  decrement: () => set((state: State) => ({ count: state.count - 1 })),
  reset: () => set(initialState),
})

const useStore = create<StoreState>((set, get) => ({
  ...initialState,
  ...createActions(set, get),
}))

Action 复用模式 #

tsx
// 通用 CRUD actions
function createCrudActions<T extends { id: string }>(set: any, get: any, key: string) {
  return {
    [`add${key}`]: (item: T) => set((state: any) => ({
      [key.toLowerCase()]: [...state[key.toLowerCase()], item]
    })),
    
    [`remove${key}`]: (id: string) => set((state: any) => ({
      [key.toLowerCase()]: state[key.toLowerCase()].filter((item: T) => item.id !== id)
    })),
    
    [`update${key}`]: (id: string, data: Partial<T>) => set((state: any) => ({
      [key.toLowerCase()]: state[key.toLowerCase()].map((item: T) =>
        item.id === id ? { ...item, ...data } : item
      )
    })),
  }
}

// 使用
const useTodoStore = create((set, get) => ({
  todos: [],
  ...createCrudActions<Todo>(set, get, 'Todos'),
}))

实际案例 #

表单状态管理 #

tsx
interface FormData {
  username: string
  email: string
  password: string
}

interface FormState {
  data: FormData
  errors: Partial<Record<keyof FormData, string>>
  touched: Partial<Record<keyof FormData, boolean>>
  isSubmitting: boolean
  
  setField: (field: keyof FormData, value: string) => void
  setTouched: (field: keyof FormData) => void
  validate: () => boolean
  submit: () => Promise<void>
  reset: () => void
}

const useFormStore = create<FormState>((set, get) => ({
  data: {
    username: '',
    email: '',
    password: '',
  },
  errors: {},
  touched: {},
  isSubmitting: false,
  
  setField: (field, value) => set((state) => ({
    data: { ...state.data, [field]: value }
  })),
  
  setTouched: (field) => set((state) => ({
    touched: { ...state.touched, [field]: true }
  })),
  
  validate: () => {
    const { data } = get()
    const errors: Partial<Record<keyof FormData, string>> = {}
    
    if (!data.username) errors.username = '用户名必填'
    if (!data.email) errors.email = '邮箱必填'
    if (!data.password) errors.password = '密码必填'
    
    set({ errors })
    return Object.keys(errors).length === 0
  },
  
  submit: async () => {
    const { validate, data } = get()
    if (!validate()) return
    
    set({ isSubmitting: true })
    
    try {
      await fetch('/api/register', {
        method: 'POST',
        body: JSON.stringify(data),
      })
      set({ isSubmitting: false })
    } catch (error) {
      set({ isSubmitting: false })
    }
  },
  
  reset: () => set({
    data: { username: '', email: '', password: '' },
    errors: {},
    touched: {},
    isSubmitting: false,
  }),
}))

最佳实践 #

1. Action 命名规范 #

tsx
// ✅ 好的命名
setUser: (user) => set({ user })
clearUser: () => set({ user: null })
updateUserName: (name) => set((state) => ({ user: { ...state.user, name } }))

// ❌ 不好的命名
update: (data) => set(data)
change: (value) => set({ value })

2. 保持 Actions 简单 #

tsx
// ✅ 好:单一职责
increment: () => set((state) => ({ count: state.count + 1 }))

// ❌ 不好:混合逻辑
increment: () => {
  set((state) => ({ count: state.count + 1 }))
  console.log('incremented')
  analytics.track('increment')
}

3. 使用 TypeScript 类型 #

tsx
// ✅ 好:完整类型定义
interface Actions {
  setUser: (user: User) => void
  updateUser: (id: string, data: Partial<User>) => void
}

总结 #

Actions 是 Zustand 状态更新的核心:

  • Actions 直接定义在 store 中
  • 使用 set 函数更新状态
  • 使用 get 函数读取当前状态
  • 遵循不可变更新原则
  • 可以使用 Immer 简化复杂更新
  • 支持批量更新和条件更新

接下来,让我们学习 异步操作,处理异步数据获取。

最后更新:2026-03-28