TypeScript 集成 #

为什么 TypeScript? #

Zustand 对 TypeScript 有原生支持,使用 TypeScript 可以:

  • 获得完整的类型推断
  • 在编译时发现错误
  • 更好的 IDE 支持
  • 更安全的重构

基本类型定义 #

简单 Store #

tsx
import { create } from 'zustand'

interface CounterState {
  count: number
  increment: () => void
  decrement: () => void
  reset: () => void
}

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

复杂类型 #

tsx
interface User {
  id: string
  name: string
  email: string
  role: 'admin' | 'user' | 'guest'
  preferences: {
    theme: 'light' | 'dark'
    language: string
    notifications: boolean
  }
}

interface UserState {
  user: User | null
  isLoading: boolean
  error: string | null
  
  login: (email: string, password: string) => Promise<void>
  logout: () => void
  updateUser: (data: Partial<User>) => void
  updatePreferences: (prefs: Partial<User['preferences']>) => void
}

const useUserStore = create<UserState>((set, get) => ({
  user: null,
  isLoading: false,
  error: null,
  
  login: async (email, password) => {
    set({ isLoading: true, error: null })
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify({ email, password }),
      })
      const user = await response.json()
      set({ user, isLoading: false })
    } catch (error) {
      set({ 
        error: error instanceof Error ? error.message : '登录失败',
        isLoading: false 
      })
    }
  },
  
  logout: () => set({ user: null, error: null }),
  
  updateUser: (data) => set((state) => ({
    user: state.user ? { ...state.user, ...data } : null
  })),
  
  updatePreferences: (prefs) => set((state) => ({
    user: state.user 
      ? { 
          ...state.user, 
          preferences: { ...state.user.preferences, ...prefs } 
        }
      : null
  })),
}))

类型推断 #

自动推断 #

Zustand 会自动推断 setget 函数的类型:

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

const useStore = create<State>((set, get) => ({
  count: 0,
  
  // set 自动推断为 (partial: State | ((state: State) => State)) => void
  increment: () => set((state) => ({ count: state.count + 1 })),
  
  // get 自动推断为 () => State
  logCount: () => {
    console.log(get().count)
  },
}))

使用 StateCreator #

tsx
import { StateCreator } from 'zustand'

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

const counterStore: StateCreator<State> = (set, get) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
})

const useStore = create(counterStore)

泛型 Store #

泛型类型定义 #

tsx
interface CrudState<T> {
  items: T[]
  selectedItem: T | null
  
  addItem: (item: T) => void
  updateItem: (id: string, data: Partial<T>) => void
  removeItem: (id: string) => void
  selectItem: (item: T | null) => void
}

function createCrudStore<T extends { id: string }>(initialItems: T[] = []) {
  return create<CrudState<T>>((set) => ({
    items: initialItems,
    selectedItem: null,
    
    addItem: (item) => set((state) => ({
      items: [...state.items, item]
    })),
    
    updateItem: (id, data) => set((state) => ({
      items: state.items.map((item) =>
        item.id === id ? { ...item, ...data } : item
      )
    })),
    
    removeItem: (id) => set((state) => ({
      items: state.items.filter((item) => item.id !== id)
    })),
    
    selectItem: (item) => set({ selectedItem: item }),
  }))
}

// 使用
interface Todo {
  id: string
  text: string
  completed: boolean
}

const useTodoStore = createCrudStore<Todo>([])

泛型选择器 #

tsx
function createSelectors<Store extends object>(store: any) {
  const useStore = store as unknown as {
    getState: () => Store
    subscribe: (listener: (state: Store) => void) => () => void
  }
  
  return {
    useStore,
    useKey: <K extends keyof Store>(key: K): Store[K] => {
      return useStore((state: Store) => state[key])
    },
    useKeys: <K extends keyof Store>(keys: K[]): Pick<Store, K> => {
      return useStore((state: Store) => {
        const result = {} as Pick<Store, K>
        keys.forEach((key) => {
          result[key] = state[key]
        })
        return result
      })
    },
  }
}

// 使用
interface State {
  count: number
  name: string
}

const store = create<State>((set) => ({
  count: 0,
  name: '',
}))

const { useKey, useKeys } = createSelectors<State>(store)

function Component() {
  const count = useKey('count')
  const { count, name } = useKeys(['count', 'name'])
}

中间件类型 #

persist 中间件 #

tsx
import { create } from 'zustand'
import { persist, createJSONStorage, StateStorage } from 'zustand/middleware'

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

// 使用类型断言
const useStore = create<State>()(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
    }),
    {
      name: 'counter-storage',
      storage: createJSONStorage(() => localStorage),
    }
  )
)

devtools 中间件 #

tsx
import { devtools } from 'zustand/middleware'

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

const useStore = create<State>()(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
    }),
    { name: 'CounterStore' }
  )
)

immer 中间件 #

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

interface State {
  user: {
    name: string
    settings: {
      theme: 'light' | 'dark'
    }
  }
  updateTheme: (theme: 'light' | 'dark') => void
}

const useStore = create<State>()(
  immer((set) => ({
    user: {
      name: '',
      settings: {
        theme: 'light',
      },
    },
    updateTheme: (theme) => set((state) => {
      state.user.settings.theme = theme
    }),
  }))
)

组合中间件类型 #

tsx
import { create } from 'zustand'
import { devtools, persist, createJSONStorage } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'

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

const useStore = create<State>()(
  devtools(
    persist(
      immer((set) => ({
        count: 0,
        increment: () => set((state) => {
          state.count += 1
        }),
      })),
      {
        name: 'counter-storage',
        storage: createJSONStorage(() => localStorage),
      }
    ),
    { name: 'CounterStore' }
  )
)

高级类型模式 #

类型安全的 Actions #

tsx
type Action<T extends string, P = void> = P extends void
  ? { type: T }
  : { type: T; payload: P }

type Actions = 
  | Action<'INCREMENT'>
  | Action<'DECREMENT'>
  | Action<'ADD', number>
  | Action<'SET', number>

interface State {
  count: number
  dispatch: (action: Actions) => void
}

const useStore = create<State>((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 'ADD':
        set((state) => ({ count: state.count + action.payload }))
        break
      case 'SET':
        set({ count: action.payload })
        break
    }
  },
}))

类型安全的 Selector #

tsx
type Selector<T, R> = (state: T) => R

interface StoreSelectors<T extends object> {
  <K extends keyof T>(key: K): Selector<T, T[K]>
  <R>(selector: Selector<T, R>): Selector<T, R>
}

function createTypedSelector<T extends object>() {
  return <R>(selector: Selector<T, R>) => selector
}

// 使用
interface State {
  count: number
  name: string
}

const useSelector = createTypedSelector<State>()

const countSelector = useSelector((state) => state.count)
const nameSelector = useSelector((state) => state.name)

类型安全的 Store Factory #

tsx
import { StateCreator, StoreApi, UseBoundStore } from 'zustand'

interface BaseState {
  loading: boolean
  error: string | null
  setLoading: (loading: boolean) => void
  setError: (error: string | null) => void
}

const createBaseState = <T extends object>(
  config: StateCreator<T & BaseState>
): UseBoundStore<StoreApi<T & BaseState>> => {
  return create<T & BaseState>((set, get, api) => ({
    loading: false,
    error: null,
    setLoading: (loading) => set({ loading }),
    setError: (error) => set({ error }),
    ...config(set as any, get as any, api),
  }))
}

// 使用
interface UserState extends BaseState {
  user: { name: string } | null
  fetchUser: (id: string) => Promise<void>
}

const useUserStore = createBaseState<UserState>((set, get) => ({
  user: null,
  
  fetchUser: async (id) => {
    set({ loading: true, error: null })
    try {
      const response = await fetch(`/api/users/${id}`)
      const user = await response.json()
      set({ user, loading: false })
    } catch (error) {
      set({ 
        error: error instanceof Error ? error.message : '获取失败',
        loading: false 
      })
    }
  },
}))

实用类型工具 #

Extract State Types #

tsx
import { UseBoundStore, StoreApi } from 'zustand'

type ExtractState<T> = T extends UseBoundStore<StoreApi<infer S>> ? S : never

// 使用
const useStore = create((set) => ({
  count: 0,
  name: '',
}))

type StoreState = ExtractState<typeof useStore>
// StoreState = { count: number; name: string }

Extract Action Types #

tsx
type OnlyActions<T> = {
  [K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never
}

interface State {
  count: number
  name: string
  increment: () => void
  setName: (name: string) => void
}

type Actions = OnlyActions<State>
// Actions = { increment: () => void; setName: (name: string) => void }

Deep Partial #

tsx
type DeepPartial<T> = T extends object
  ? { [P in keyof T]?: DeepPartial<T[P]> }
  : T

interface User {
  profile: {
    name: string
    settings: {
      theme: string
    }
  }
}

interface State {
  user: User
  updateUser: (data: DeepPartial<User>) => void
}

const useStore = create<State>((set) => ({
  user: {
    profile: {
      name: '',
      settings: {
        theme: 'light',
      },
    },
  },
  
  updateUser: (data) => set((state) => ({
    user: {
      ...state.user,
      ...data,
      profile: {
        ...state.user.profile,
        ...(data.profile || {}),
        settings: {
          ...state.user.profile.settings,
          ...(data.profile?.settings || {}),
        },
      },
    },
  })),
}))

实际案例 #

完整的类型安全 Store #

tsx
import { create } from 'zustand'
import { devtools, persist, createJSONStorage } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'

// 类型定义
interface Todo {
  id: string
  text: string
  completed: boolean
  createdAt: number
}

type Filter = 'all' | 'active' | 'completed'

interface TodoState {
  todos: Todo[]
  filter: Filter
  
  // Actions
  addTodo: (text: string) => void
  toggleTodo: (id: string) => void
  removeTodo: (id: string) => void
  setFilter: (filter: Filter) => void
  clearCompleted: () => void
  
  // Computed
  getFilteredTodos: () => Todo[]
  getStats: () => { total: number; active: number; completed: number }
}

// Store 创建
const useTodoStore = create<TodoState>()(
  devtools(
    persist(
      immer((set, get) => ({
        todos: [],
        filter: 'all',
        
        addTodo: (text) => set((state) => {
          state.todos.push({
            id: crypto.randomUUID(),
            text,
            completed: false,
            createdAt: Date.now(),
          })
        }),
        
        toggleTodo: (id) => set((state) => {
          const todo = state.todos.find((t) => t.id === id)
          if (todo) todo.completed = !todo.completed
        }),
        
        removeTodo: (id) => set((state) => {
          const index = state.todos.findIndex((t) => t.id === id)
          if (index !== -1) state.todos.splice(index, 1)
        }),
        
        setFilter: (filter) => set({ filter }),
        
        clearCompleted: () => set((state) => {
          state.todos = state.todos.filter((t) => !t.completed)
        }),
        
        getFilteredTodos: () => {
          const { todos, filter } = get()
          switch (filter) {
            case 'active':
              return todos.filter((t) => !t.completed)
            case 'completed':
              return todos.filter((t) => t.completed)
            default:
              return todos
          }
        },
        
        getStats: () => {
          const { todos } = get()
          return {
            total: todos.length,
            active: todos.filter((t) => !t.completed).length,
            completed: todos.filter((t) => t.completed).length,
          }
        },
      })),
      {
        name: 'todo-storage',
        storage: createJSONStorage(() => localStorage),
        partialize: (state) => ({
          todos: state.todos,
          filter: state.filter,
        }),
      }
    ),
    { name: 'TodoStore' }
  )
)

// 类型安全的组件使用
function TodoList() {
  const todos = useTodoStore((state) => state.getFilteredTodos())
  const toggleTodo = useTodoStore((state) => state.toggleTodo)
  const removeTodo = useTodoStore((state) => state.removeTodo)
  
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          <input
            type="checkbox"
            checked={todo.completed}
            onChange={() => toggleTodo(todo.id)}
          />
          <span>{todo.text}</span>
          <button onClick={() => removeTodo(todo.id)}>删除</button>
        </li>
      ))}
    </ul>
  )
}

常见类型问题 #

问题1:中间件类型推断 #

tsx
// ❌ 类型推断失败
const useStore = create(
  persist((set) => ({ count: 0 }), { name: 'store' })
)

// ✅ 使用类型断言
const useStore = create<State>()(
  persist((set) => ({ count: 0 }), { name: 'store' })
)

问题2:set 函数类型 #

tsx
// ❌ 类型错误
interface State {
  count: number
  increment: () => void
}

const useStore = create<State>((set) => ({
  count: 0,
  increment: () => set({ count: 'string' }), // 类型错误
}))

// ✅ 正确类型
increment: () => set((state) => ({ count: state.count + 1 }))

问题3:选择器返回类型 #

tsx
// ❌ 返回类型不明确
const data = useStore((state) => ({
  count: state.count,
  double: state.count * 2,
}))

// ✅ 明确类型
interface SelectedData {
  count: number
  double: number
}

const data = useStore((state): SelectedData => ({
  count: state.count,
  double: state.count * 2,
}))

总结 #

TypeScript 集成的关键点:

  • 定义完整的 State 接口
  • 使用类型断言处理中间件
  • 利用类型推断减少样板代码
  • 使用泛型创建可复用的 Store
  • 使用类型工具提取和转换类型

接下来,让我们学习 开发调试,掌握调试技巧。

最后更新:2026-03-28