ZustandTypeScript集成Skill zustand-typescript

这个技能专注于在TypeScript项目中集成Zustand状态管理库,提供类型安全的商店创建、选择器定义、切片模式、中间件使用等高级模式,帮助前端开发者构建可维护、高效和类型安全的React应用。关键词:Zustand, TypeScript, 状态管理, 类型安全, 前端开发, React, 商店创建, 选择器, 切片模式, 中间件。

前端开发 0 次安装 0 次浏览 更新于 3/25/2026

name: zustand-typescript user-invocable: false description: 在TypeScript项目中使用Zustand时使用。涵盖类型安全的商店创建、类型化选择器,以及Zustand的高级TypeScript模式。 allowed-tools:

  • 读取
  • 写入
  • 编辑
  • Bash
  • Grep
  • Glob

Zustand - TypeScript集成

Zustand 开箱即用,对TypeScript有出色的支持。此技能涵盖使用Zustand与TypeScript时的类型安全模式和最佳实践。

关键概念

基本类型安全商店

定义您的商店接口,并与 create 一起使用:

import { create } from 'zustand'

interface BearStore {
  bears: number
  increasePopulation: () => void
  removeAllBears: () => void
  updateBears: (newBears: number) => void
}

const useBearStore = create<BearStore>()((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
  updateBears: (newBears) => set({ bears: newBears }),
}))

类型推断

Zustand 可以自动推断类型:

const useStore = create((set) => ({
  count: 0,
  text: '',
  increment: () => set((state) => ({ count: state.count + 1 })),
  setText: (text: string) => set({ text }),
}))

// 类型自动推断
type Store = ReturnType<typeof useStore.getState>
// {
//   count: number
//   text: string
//   increment: () => void
//   setText: (text: string) => void
// }

最佳实践

1. 定义商店接口

始终定义显式接口以获得更好的类型安全和IDE支持:

interface User {
  id: string
  name: string
  email: string
}

interface UserStore {
  // 状态
  users: User[]
  selectedUserId: string | null
  isLoading: boolean
  error: string | null

  // 计算属性
  selectedUser: User | null

  // 操作
  fetchUsers: () => Promise<void>
  selectUser: (id: string) => void
  clearSelection: () => void
}

const useUserStore = create<UserStore>()((set, get) => ({
  users: [],
  selectedUserId: null,
  isLoading: false,
  error: null,

  get selectedUser() {
    const { users, selectedUserId } = get()
    return users.find((u) => u.id === selectedUserId) ?? null
  },

  fetchUsers: async () => {
    set({ isLoading: true, error: null })
    try {
      const users = await api.fetchUsers()
      set({ users, isLoading: false })
    } catch (error) {
      set({ error: error.message, isLoading: false })
    }
  },

  selectUser: (id) => set({ selectedUserId: id }),
  clearSelection: () => set({ selectedUserId: null }),
}))

2. 类型安全选择器

创建类型化选择器函数以用于可重用逻辑:

interface TodoStore {
  todos: Todo[]
  filter: 'all' | 'active' | 'completed'
  addTodo: (text: string) => void
  toggleTodo: (id: string) => void
  setFilter: (filter: TodoStore['filter']) => void
}

const useTodoStore = create<TodoStore>()(/* ... */)

// 类型化选择器函数
const selectFilteredTodos = (state: TodoStore) => {
  if (state.filter === 'all') return state.todos
  if (state.filter === 'active') return state.todos.filter((t) => !t.completed)
  return state.todos.filter((t) => t.completed)
}

const selectActiveTodoCount = (state: TodoStore) =>
  state.todos.filter((t) => !t.completed).length

// 使用
function TodoList() {
  const filteredTodos = useTodoStore(selectFilteredTodos)
  const activeCount = useTodoStore(selectActiveTodoCount)

  return (
    <div>
      <p>{activeCount} 个活跃待办事项</p>
      {filteredTodos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </div>
  )
}

3. 切片模式与类型

用于大型应用程序的类型安全商店切片:

import { StateCreator } from 'zustand'

interface BearSlice {
  bears: number
  addBear: () => void
  eatFish: () => void
}

interface FishSlice {
  fishes: number
  addFish: () => void
}

interface SharedSlice {
  addBoth: () => void
  getBoth: () => number
}

const createBearSlice: StateCreator<
  BearSlice & FishSlice,
  [],
  [],
  BearSlice
> = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
  eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})

const createFishSlice: StateCreator<
  BearSlice & FishSlice,
  [],
  [],
  FishSlice
> = (set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

const createSharedSlice: StateCreator<
  BearSlice & FishSlice,
  [],
  [],
  SharedSlice
> = (set, get) => ({
  addBoth: () => {
    get().addBear()
    get().addFish()
  },
  getBoth: () => get().bears + get().fishes,
})

const useBoundStore = create<BearSlice & FishSlice & SharedSlice>()(
  (...a) => ({
    ...createBearSlice(...a),
    ...createFishSlice(...a),
    ...createSharedSlice(...a),
  })
)

4. 通用商店工厂

使用泛型创建可重用商店工厂:

import { create, StoreApi } from 'zustand'

interface AsyncState<T> {
  data: T | null
  isLoading: boolean
  error: string | null
}

interface AsyncActions<T> {
  fetch: () => Promise<void>
  reset: () => void
}

type AsyncStore<T> = AsyncState<T> & AsyncActions<T>

function createAsyncStore<T>(
  fetcher: () => Promise<T>
): StoreApi<AsyncStore<T>> {
  return create<AsyncStore<T>>()((set) => ({
    data: null,
    isLoading: false,
    error: null,

    fetch: async () => {
      set({ isLoading: true, error: null })
      try {
        const data = await fetcher()
        set({ data, isLoading: false })
      } catch (error) {
        set({
          error: error instanceof Error ? error.message : '未知错误',
          isLoading: false,
        })
      }
    },

    reset: () => set({ data: null, isLoading: false, error: null }),
  }))
}

// 使用
interface User {
  id: string
  name: string
}

const useUserStore = createAsyncStore<User[]>(() =>
  fetch('/api/users').then((r) => r.json())
)

5. 类型安全中间件

正确类型化中间件以获得完全类型安全:

import { create } from 'zustand'
import { persist, devtools } from 'zustand/middleware'
import type { PersistOptions } from 'zustand/middleware'

interface MyStore {
  count: number
  increment: () => void
}

type MyPersist = (
  config: StateCreator<MyStore>,
  options: PersistOptions<MyStore>
) => StateCreator<MyStore>

const useStore = create<MyStore>()(
  devtools(
    persist(
      (set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 })),
      }),
      {
        name: 'my-store',
      }
    )
  )
)

示例

类型安全CRUD商店

interface Entity {
  id: string
  name: string
  createdAt: Date
}

interface CrudStore<T extends Entity> {
  items: T[]
  selectedId: string | null
  isLoading: boolean
  error: string | null

  // 计算属性
  selectedItem: T | null

  // 操作
  fetchAll: () => Promise<void>
  fetchOne: (id: string) => Promise<void>
  create: (data: Omit<T, 'id' | 'createdAt'>) => Promise<void>
  update: (id: string, data: Partial<T>) => Promise<void>
  delete: (id: string) => Promise<void>
  select: (id: string | null) => void
}

function createCrudStore<T extends Entity>(
  apiEndpoint: string
): StoreApi<CrudStore<T>> {
  return create<CrudStore<T>>()((set, get) => ({
    items: [],
    selectedId: null,
    isLoading: false,
    error: null,

    get selectedItem() {
      const { items, selectedId } = get()
      return items.find((item) => item.id === selectedId) ?? null
    },

    fetchAll: async () => {
      set({ isLoading: true, error: null })
      try {
        const response = await fetch(apiEndpoint)
        const items = await response.json()
        set({ items, isLoading: false })
      } catch (error) {
        set({ error: error.message, isLoading: false })
      }
    },

    fetchOne: async (id) => {
      set({ isLoading: true, error: null })
      try {
        const response = await fetch(`${apiEndpoint}/${id}`)
        const item = await response.json()
        set((state) => ({
          items: state.items.some((i) => i.id === id)
            ? state.items.map((i) => (i.id === id ? item : i))
            : [...state.items, item],
          isLoading: false,
        }))
      } catch (error) {
        set({ error: error.message, isLoading: false })
      }
    },

    create: async (data) => {
      set({ isLoading: true, error: null })
      try {
        const response = await fetch(apiEndpoint, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(data),
        })
        const newItem = await response.json()
        set((state) => ({
          items: [...state.items, newItem],
          isLoading: false,
        }))
      } catch (error) {
        set({ error: error.message, isLoading: false })
      }
    },

    update: async (id, data) => {
      set({ isLoading: true, error: null })
      try {
        const response = await fetch(`${apiEndpoint}/${id}`, {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(data),
        })
        const updatedItem = await response.json()
        set((state) => ({
          items: state.items.map((item) =>
            item.id === id ? updatedItem : item
          ),
          isLoading: false,
        }))
      } catch (error) {
        set({ error: error.message, isLoading: false })
      }
    },

    delete: async (id) => {
      set({ isLoading: true, error: null })
      try {
        await fetch(`${apiEndpoint}/${id}`, { method: 'DELETE' })
        set((state) => ({
          items: state.items.filter((item) => item.id !== id),
          selectedId: state.selectedId === id ? null : state.selectedId,
          isLoading: false,
        }))
      } catch (error) {
        set({ error: error.message, isLoading: false })
      }
    },

    select: (id) => set({ selectedId: id }),
  }))
}

// 使用
interface Product extends Entity {
  price: number
  description: string
}

const useProductStore = createCrudStore<Product>('/api/products')

强类型操作

使用判别联合体实现类型安全操作模式:

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'set'; value: number }
  | { type: 'reset' }

interface CounterStore {
  count: number
  dispatch: (action: Action) => void
}

const useCounterStore = create<CounterStore>()((set) => ({
  count: 0,

  dispatch: (action) => {
    switch (action.type) {
      case 'increment':
        set((state) => ({ count: state.count + 1 }))
        break
      case 'decrement':
        set((state) => ({ count: state.count - 1 }))
        break
      case 'set':
        set({ count: action.value })
        break
      case 'reset':
        set({ count: 0 })
        break
    }
  },
}))

// 使用完全类型安全
const dispatch = useCounterStore((state) => state.dispatch)
dispatch({ type: 'set', value: 10 }) // ✅ 类型安全
dispatch({ type: 'set', value: 'abc' }) // ❌ TypeScript错误

常见模式

大型商店的命名空间模式

组织相关状态和操作:

interface Store {
  auth: {
    user: User | null
    token: string | null
    login: (credentials: Credentials) => Promise<void>
    logout: () => void
  }
  cart: {
    items: CartItem[]
    addItem: (item: Product) => void
    removeItem: (id: string) => void
    clear: () => void
  }
}

const useStore = create<Store>()((set) => ({
  auth: {
    user: null,
    token: null,
    login: async (credentials) => {
      const { user, token } = await api.login(credentials)
      set((state) => ({
        auth: { ...state.auth, user, token },
      }))
    },
    logout: () =>
      set((state) => ({
        auth: { ...state.auth, user: null, token: null },
      })),
  },
  cart: {
    items: [],
    addItem: (product) =>
      set((state) => ({
        cart: {
          ...state.cart,
          items: [...state.cart.items, { ...product, quantity: 1 }],
        },
      })),
    removeItem: (id) =>
      set((state) => ({
        cart: {
          ...state.cart,
          items: state.cart.items.filter((item) => item.id !== id),
        },
      })),
    clear: () =>
      set((state) => ({
        cart: { ...state.cart, items: [] },
      })),
  },
}))

// 使用
const login = useStore((state) => state.auth.login)
const cartItems = useStore((state) => state.cart.items)

类型安全事件发射器

创建类型化事件系统:

type Events = {
  'user:login': { userId: string; timestamp: Date }
  'user:logout': { userId: string }
  'cart:add': { productId: string; quantity: number }
  'cart:remove': { productId: string }
}

type EventListener<T extends keyof Events> = (data: Events[T]) => void

interface EventStore {
  listeners: {
    [K in keyof Events]?: EventListener<K>[]
  }
  on: <T extends keyof Events>(event: T, listener: EventListener<T>) => void
  off: <T extends keyof Events>(event: T, listener: EventListener<T>) => void
  emit: <T extends keyof Events>(event: T, data: Events[T]) => void
}

const useEventStore = create<EventStore>()((set, get) => ({
  listeners: {},

  on: (event, listener) => {
    set((state) => ({
      listeners: {
        ...state.listeners,
        [event]: [...(state.listeners[event] || []), listener],
      },
    }))
  },

  off: (event, listener) => {
    set((state) => ({
      listeners: {
        ...state.listeners,
        [event]: (state.listeners[event] || []).filter((l) => l !== listener),
      },
    }))
  },

  emit: (event, data) => {
    const listeners = get().listeners[event] || []
    listeners.forEach((listener) => listener(data))
  },
}))

反模式

❌ 不要使用 any 类型

// 坏
const useStore = create<any>()((set) => ({
  data: null,
  setData: (data: any) => set({ data }),
}))

// 好
interface Store {
  data: User | null
  setData: (data: User | null) => void
}

const useStore = create<Store>()((set) => ({
  data: null,
  setData: (data) => set({ data }),
}))

❌ 不要忽略返回类型

// 坏: 忽略返回类型
const useStore = create((set) => ({
  fetch: async () => {
    const data = await api.fetch()
    set({ data })
    // 缺少返回类型
  },
}))

// 好: 显式返回类型
interface Store {
  fetch: () => Promise<void>
}

const useStore = create<Store>()((set) => ({
  fetch: async (): Promise<void> => {
    const data = await api.fetch()
    set({ data })
  },
}))

❌ 不要在类型中混合状态和操作

// 坏: 混淆结构
interface Store {
  count: number
  increment: () => void
  name: string
  setName: (name: string) => void
}

// 好: 分离关注点
interface StoreState {
  count: number
  name: string
}

interface StoreActions {
  increment: () => void
  setName: (name: string) => void
}

type Store = StoreState & StoreActions

相关技能

  • zustand-store-patterns: 基本商店创建和使用
  • zustand-middleware: 使用TypeScript与中间件
  • zustand-advanced-patterns: 高级TypeScript模式和技术