TypeScript 类型定义指南
Zustand 提供了出色的 TypeScript 支持,允许你为 Store 定义严格的类型,从而获得更好的开发体验和类型安全。本指南将详细介绍如何在 Zustand 中使用 TypeScript 进行类型定义。
基本类型定义
自动类型推断
Zustand 可以自动推断 Store 的类型,无需显式定义:
typescript
import { create } from 'zustand'
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}))
// TypeScript 会自动推断以下类型:
// useCounterStore: () => {
// count: number;
// increment: () => void;
// decrement: () => void;
// reset: () => void;
// }显式类型定义
对于更复杂的 Store,你可以显式定义 Store 的类型:
typescript
import { create } from 'zustand'
// 定义 Store 类型
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
setCount: (count: number) => void;
}
// 创建 Store 并应用类型
const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
setCount: (count) => set({ count }),
}))类型化 Actions
基本 Actions 类型
typescript
import { create } from 'zustand'
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoStore {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
deleteTodo: (id: number) => void;
updateTodo: (id: number, text: string) => void;
}
const useTodoStore = create<TodoStore>((set) => ({
todos: [],
addTodo: (text) => set((state) => ({
todos: [...state.todos, { id: Date.now(), text, completed: false }],
})),
toggleTodo: (id) => set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
})),
deleteTodo: (id) => set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
})),
updateTodo: (id, text) => set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, text } : todo
),
})),
}))使用 SetState 类型
你可以使用 SetState 类型来定义 set 函数的类型:
typescript
import { create, SetState } from 'zustand'
interface CounterStore {
count: number;
increment: () => void;
multiply: (factor: number) => void;
}
const createCounterStore = (set: SetState<CounterStore>) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
multiply: (factor) => set((state) => ({ count: state.count * factor })),
})
const useCounterStore = create<CounterStore>(createCounterStore)异步 Actions 类型
typescript
import { create } from 'zustand'
interface User {
id: number;
name: string;
email: string;
}
interface UserStore {
users: User[];
isLoading: boolean;
error: string | null;
fetchUsers: () => Promise<void>;
fetchUser: (id: number) => Promise<User | null>;
addUser: (user: Omit<User, 'id'>) => Promise<void>;
}
const useUserStore = create<UserStore>((set, get) => ({
users: [],
isLoading: false,
error: null,
fetchUsers: async () => {
set({ isLoading: true, error: null })
try {
const response = await fetch('/api/users')
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({ isLoading: false })
return user
} catch (error) {
set({ error: error instanceof Error ? error.message : '未知错误', isLoading: false })
return null
}
},
addUser: 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),
})
const newUser = await response.json()
set((state) => ({ users: [...state.users, newUser], isLoading: false }))
} catch (error) {
set({ error: error instanceof Error ? error.message : '未知错误', isLoading: false })
}
},
}))中间件类型
Persist 中间件类型
typescript
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
interface CounterStore {
count: number;
increment: () => void;
}
const useCounterStore = create<CounterStore>()(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'counter-storage',
storage: createJSONStorage(() => localStorage),
}
)
)Immer 中间件类型
typescript
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoStore {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
}
const useTodoStore = create<TodoStore>()(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
state.todos.push({ id: Date.now(), text, completed: false })
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.find((todo) => todo.id === id)
if (todo) todo.completed = !todo.completed
}),
}))
)组合中间件类型
typescript
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
interface CounterStore {
count: number;
increment: () => void;
multiply: (factor: number) => void;
}
const useCounterStore = create<CounterStore>()(
devtools(
persist(
immer((set) => ({
count: 0,
increment: () => set((state) => { state.count += 1 }),
multiply: (factor) => set((state) => { state.count *= factor }),
})),
{
name: 'counter-storage',
}
)
)
)Selector 类型
基本 Selector 类型
typescript
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'
interface User {
id: number;
name: string;
email: string;
age: number;
}
interface UserStore {
users: User[];
selectedUserId: number | null;
selectUser: (id: number) => void;
}
const useUserStore = create<UserStore>((set) => ({
users: [],
selectedUserId: null,
selectUser: (id) => set({ selectedUserId: id }),
}))
// 使用 Selector
function UserProfile() {
// 基本 Selector
const selectedUserId = useUserStore((state) => state.selectedUserId)
// 组合 Selector
const { name, email } = useUserStore(
(state) => {
const user = state.users.find((u) => u.id === state.selectedUserId)
return { name: user?.name || '', email: user?.email || '' }
},
useShallow() // 浅比较
)
// ...
}自定义 Selector 类型
typescript
import { create } from 'zustand'
interface CounterStore {
count: number;
increment: () => void;
}
const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
// 自定义 Selector 类型
const useCount = () => useCounterStore((state) => state.count)
const useIncrement = () => useCounterStore((state) => state.increment)
// 在组件中使用
function Counter() {
const count = useCount()
const increment = useIncrement()
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
</div>
)
}高级类型模式
使用 createStore 类型
typescript
import { create, createStore } from 'zustand'
interface CounterStore {
count: number;
increment: () => void;
}
// 创建 Store 实例
const counterStore = createStore<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
// 创建 Hook
const useCounterStore = create<CounterStore>(() => counterStore)
// 直接使用 Store 实例(非 React 环境)
counterStore.getState().increment()扩展 Store 类型
typescript
import { create } from 'zustand'
interface BaseCounterStore {
count: number;
increment: () => void;
}
interface ExtendedCounterStore extends BaseCounterStore {
multiply: (factor: number) => void;
reset: () => void;
}
const useBaseCounterStore = create<BaseCounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
const useExtendedCounterStore = create<ExtendedCounterStore>((set, get) => ({
...useBaseCounterStore.getState(),
multiply: (factor) => set((state) => ({ count: state.count * factor })),
reset: () => set({ count: 0 }),
}))依赖注入类型
typescript
import { create } from 'zustand'
interface Config {
apiUrl: string;
}
interface UserStore {
users: User[];
fetchUsers: () => Promise<void>;
}
interface User {
id: number;
name: string;
}
const createUserStore = (config: Config) => {
return create<UserStore>((set) => ({
users: [],
fetchUsers: async () => {
const response = await fetch(`${config.apiUrl}/users`)
const users = await response.json()
set({ users })
},
}))
}
// 使用
const useUserStore = createUserStore({ apiUrl: 'https://api.example.com' })类型安全的 State 更新
使用 Partial 更新部分状态
typescript
import { create } from 'zustand'
interface UserStore {
user: {
id: number;
name: string;
email: string;
age: number;
};
updateUser: (updates: Partial<UserStore['user']>) => void;
}
const useUserStore = create<UserStore>((set) => ({
user: {
id: 1,
name: '张三',
email: '[email protected]',
age: 30,
},
updateUser: (updates) =>
set((state) => ({
user: { ...state.user, ...updates },
})),
}))
// 使用
useUserStore.getState().updateUser({ name: '李四' }) // 类型安全使用 Omit 和 Pick 类型
typescript
import { create } from 'zustand'
interface Todo {
id: number;
text: string;
completed: boolean;
createdAt: Date;
updatedAt: Date;
}
interface TodoStore {
todos: Todo[];
addTodo: (todo: Omit<Todo, 'id' | 'createdAt' | 'updatedAt'>) => void;
updateTodo: (id: number, updates: Pick<Todo, 'text' | 'completed'>) => void;
}
const useTodoStore = create<TodoStore>((set) => ({
todos: [],
addTodo: (todo) =>
set((state) => ({
todos: [
...state.todos,
{
...todo,
id: Date.now(),
createdAt: new Date(),
updatedAt: new Date(),
},
],
})),
updateTodo: (id, updates) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id
? { ...todo, ...updates, updatedAt: new Date() }
: todo
),
})),
}))最佳实践
1. 显式定义 Store 类型
虽然 Zustand 支持自动类型推断,但显式定义 Store 类型可以使代码更清晰,尤其是对于复杂的 Store:
typescript
// 推荐:显式定义类型
interface CounterStore {
count: number;
increment: () => void;
}
const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))2. 使用 readonly 保护不可变状态
typescript
import { create } from 'zustand'
interface Todo {
readonly id: number;
text: string;
completed: boolean;
readonly createdAt: Date;
}
interface TodoStore {
readonly todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
}3. 分离类型定义和实现
将类型定义放在单独的文件中,可以提高代码的可维护性:
typescript
// store/types.ts
export interface Todo {
id: number;
text: string;
completed: boolean;
}
export interface TodoStore {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
}
// store/index.ts
import { create } from 'zustand'
import { TodoStore } from './types'
export const useTodoStore = create<TodoStore>((set) => ({
// 实现
}))4. 使用 unknown 处理外部数据
typescript
import { create } from 'zustand'
interface User {
id: number;
name: string;
email: string;
}
interface UserStore {
users: User[];
fetchUsers: () => Promise<void>;
}
const useUserStore = create<UserStore>((set) => ({
users: [],
fetchUsers: async () => {
const response = await fetch('/api/users')
const data: unknown = await response.json()
// 验证数据类型
if (Array.isArray(data) && data.every(item =>
typeof item === 'object' && item !== null && 'id' in item
)) {
set({ users: data as User[] })
}
},
}))5. 为中间件配置添加类型
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface CounterStore {
count: number;
increment: () => void;
}
const useCounterStore = create<CounterStore>()(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'counter-storage',
// 为 storage 配置添加类型
storage: {
getItem: (name) => {
const item = localStorage.getItem(name)
return item ? JSON.parse(item) : null
},
setItem: (name, value) => {
localStorage.setItem(name, JSON.stringify(value))
},
removeItem: (name) => {
localStorage.removeItem(name)
},
},
}
)
)常见问题与解决方案
1. 类型错误:set 函数无法推断状态类型
问题:
typescript
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
// 类型错误:state.count 可能为 undefined
}))解决方案:显式定义 Store 类型
typescript
interface CounterStore {
count: number;
increment: () => void;
}
const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))2. 类型错误:中间件组合时类型不匹配
问题:
typescript
const useCounterStore = create(
devtools(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
})
)
)
)解决方案:使用泛型工厂函数语法
typescript
const useCounterStore = create<CounterStore>()(
devtools(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
})
)
)
)3. 类型错误:Selector 返回类型不明确
问题:
typescript
const userData = useUserStore((state) => {
const user = state.users.find(u => u.id === state.selectedUserId)
return { name: user?.name, email: user?.email }
})解决方案:显式定义 Selector 返回类型
typescript
const userData = useUserStore<{ name: string | undefined; email: string | undefined }>(
(state) => {
const user = state.users.find(u => u.id === state.selectedUserId)
return { name: user?.name, email: user?.email }
}
)总结
Zustand 的 TypeScript 支持提供了强大的类型安全功能,使你能够:
- 定义严格的 Store 类型:确保状态和操作的类型一致性
- 自动类型推断:减少样板代码,提高开发效率
- 中间件类型支持:为 persist、immer 等中间件提供类型定义
- 类型安全的状态更新:确保状态更新符合预期的类型
- 高级类型模式:支持扩展 Store、依赖注入等高级模式
通过遵循本指南中的最佳实践,你可以充分利用 TypeScript 的优势,构建更可靠、更易于维护的 Zustand 应用。
在接下来的章节中,我们将学习 非 React 组件中使用 Store,了解如何在非 React 环境中使用 Zustand Store。