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 会自动推断 set 和 get 函数的类型:
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