Store 切片模式 #
什么是切片模式? #
切片模式是将大型 Store 拆分成多个独立、可复用模块的设计模式。每个切片负责一个特定领域的状态和逻辑。
为什么需要切片模式? #
text
大型应用状态管理挑战
├── Store 过大 ────── 难以维护和理解
├── 逻辑耦合 ─────── 不同领域混杂
├── 复用困难 ─────── 无法独立使用
└── 协作冲突 ─────── 多人开发困难
基本切片模式 #
定义切片 #
tsx
import { StateCreator } from 'zustand'
// 用户切片
interface UserSlice {
user: {
id: string
name: string
email: string
} | null
setUser: (user: UserSlice['user']) => void
clearUser: () => void
}
const createUserSlice: StateCreator<UserSlice> = (set) => ({
user: null,
setUser: (user) => set({ user }),
clearUser: () => set({ user: null }),
})
// 计数器切片
interface CounterSlice {
count: number
increment: () => void
decrement: () => void
}
const createCounterSlice: StateCreator<CounterSlice> = (set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
})
组合切片 #
tsx
import { create } from 'zustand'
type StoreState = UserSlice & CounterSlice
const useStore = create<StoreState>()((...a) => ({
...createUserSlice(...a),
...createCounterSlice(...a),
}))
完整的切片模式实现 #
切片类型定义 #
tsx
// types/slices.ts
import { StateCreator } from 'zustand'
export type SliceCreator<T> = StateCreator<T, [], [], T>
export interface SliceConfig<T> {
name: string
create: SliceCreator<T>
dependencies?: string[]
}
用户切片 #
tsx
// slices/userSlice.ts
import { SliceCreator } from '../types/slices'
export interface User {
id: string
name: string
email: string
avatar?: string
}
export interface UserSlice {
user: User | null
isAuthenticated: boolean
isLoading: boolean
error: string | null
// Actions
login: (email: string, password: string) => Promise<void>
logout: () => void
updateUser: (data: Partial<User>) => void
clearError: () => void
}
export const createUserSlice: SliceCreator<UserSlice> = (set, get) => ({
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
login: async (email, password) => {
set({ isLoading: true, error: null })
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
if (!response.ok) {
throw new Error('登录失败')
}
const user = await response.json()
set({ user, isAuthenticated: true, isLoading: false })
} catch (error) {
set({
error: error instanceof Error ? error.message : '登录失败',
isLoading: false,
})
}
},
logout: () => {
set({ user: null, isAuthenticated: false, error: null })
},
updateUser: (data) => {
const { user } = get()
if (user) {
set({ user: { ...user, ...data } })
}
},
clearError: () => set({ error: null }),
})
购物车切片 #
tsx
// slices/cartSlice.ts
import { SliceCreator } from '../types/slices'
export interface CartItem {
productId: string
name: string
price: number
quantity: number
image: string
}
export interface CartSlice {
items: CartItem[]
totalItems: number
totalPrice: number
// Actions
addItem: (item: Omit<CartItem, 'quantity'>) => void
removeItem: (productId: string) => void
updateQuantity: (productId: string, quantity: number) => void
clearCart: () => void
}
export const createCartSlice: SliceCreator<CartSlice> = (set, get) => ({
items: [],
totalItems: 0,
totalPrice: 0,
addItem: (item) => set((state) => {
const existingItem = state.items.find(i => i.productId === item.productId)
let newItems: CartItem[]
if (existingItem) {
newItems = state.items.map(i =>
i.productId === item.productId
? { ...i, quantity: i.quantity + 1 }
: i
)
} else {
newItems = [...state.items, { ...item, quantity: 1 }]
}
return {
items: newItems,
totalItems: newItems.reduce((sum, i) => sum + i.quantity, 0),
totalPrice: newItems.reduce((sum, i) => sum + i.price * i.quantity, 0),
}
}),
removeItem: (productId) => set((state) => {
const newItems = state.items.filter(i => i.productId !== productId)
return {
items: newItems,
totalItems: newItems.reduce((sum, i) => sum + i.quantity, 0),
totalPrice: newItems.reduce((sum, i) => sum + i.price * i.quantity, 0),
}
}),
updateQuantity: (productId, quantity) => {
if (quantity <= 0) {
return get().removeItem(productId)
}
set((state) => {
const newItems = state.items.map(i =>
i.productId === productId ? { ...i, quantity } : i
)
return {
items: newItems,
totalItems: newItems.reduce((sum, i) => sum + i.quantity, 0),
totalPrice: newItems.reduce((sum, i) => sum + i.price * i.quantity, 0),
}
})
},
clearCart: () => set({
items: [],
totalItems: 0,
totalPrice: 0,
}),
})
主题切片 #
tsx
// slices/themeSlice.ts
import { SliceCreator } from '../types/slices'
export type Theme = 'light' | 'dark' | 'system'
export interface ThemeSlice {
theme: Theme
resolvedTheme: 'light' | 'dark'
// Actions
setTheme: (theme: Theme) => void
toggleTheme: () => void
}
const getSystemTheme = (): 'light' | 'dark' => {
if (typeof window === 'undefined') return 'light'
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
const resolveTheme = (theme: Theme): 'light' | 'dark' => {
return theme === 'system' ? getSystemTheme() : theme
}
export const createThemeSlice: SliceCreator<ThemeSlice> = (set, get) => ({
theme: 'system',
resolvedTheme: resolveTheme('system'),
setTheme: (theme) => set({
theme,
resolvedTheme: resolveTheme(theme),
}),
toggleTheme: () => {
const { resolvedTheme } = get()
const newTheme = resolvedTheme === 'light' ? 'dark' : 'light'
set({ theme: newTheme, resolvedTheme: newTheme })
},
})
组合所有切片 #
tsx
// store/index.ts
import { create } from 'zustand'
import { devtools, persist, createJSONStorage } from 'zustand/middleware'
import { createUserSlice, UserSlice } from './slices/userSlice'
import { createCartSlice, CartSlice } from './slices/cartSlice'
import { createThemeSlice, ThemeSlice } from './slices/themeSlice'
type StoreState = UserSlice & CartSlice & ThemeSlice
export const useStore = create<StoreState>()(
devtools(
persist(
(...a) => ({
...createUserSlice(...a),
...createCartSlice(...a),
...createThemeSlice(...a),
}),
{
name: 'app-storage',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({
theme: state.theme,
items: state.items,
}),
}
),
{ name: 'AppStore' }
)
)
切片间通信 #
通过 get 函数访问其他切片 #
tsx
// slices/orderSlice.ts
import { SliceCreator } from '../types/slices'
import { CartItem } from './cartSlice'
export interface OrderSlice {
orders: Order[]
createOrder: () => Promise<void>
}
export const createOrderSlice: SliceCreator<OrderSlice & CartSlice> = (set, get) => ({
orders: [],
createOrder: async () => {
// 访问购物车切片的状态
const { items, totalPrice, clearCart } = get()
if (items.length === 0) {
throw new Error('购物车为空')
}
const response = await fetch('/api/orders', {
method: 'POST',
body: JSON.stringify({ items, totalPrice }),
})
const order = await response.json()
// 更新订单切片
set((state) => ({
orders: [...state.orders, order],
}))
// 清空购物车
clearCart()
},
})
使用中间件共享状态 #
tsx
// slices/notificationSlice.ts
import { SliceCreator } from '../types/slices'
export interface Notification {
id: string
type: 'success' | 'error' | 'warning' | 'info'
message: string
}
export interface NotificationSlice {
notifications: Notification[]
addNotification: (notification: Omit<Notification, 'id'>) => void
removeNotification: (id: string) => void
clearNotifications: () => void
}
export const createNotificationSlice: SliceCreator<NotificationSlice> = (set) => ({
notifications: [],
addNotification: (notification) => set((state) => ({
notifications: [
...state.notifications,
{ ...notification, id: Date.now().toString() },
],
})),
removeNotification: (id) => set((state) => ({
notifications: state.notifications.filter(n => n.id !== id),
})),
clearNotifications: () => set({ notifications: [] }),
}))
// 在其他切片中使用通知
export const createUserSliceWithNotifications: SliceCreator<UserSlice & NotificationSlice> =
(set, get) => ({
// ... 用户切片逻辑
login: async (email, password) => {
set({ isLoading: true, error: null })
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
})
if (!response.ok) throw new Error('登录失败')
const user = await response.json()
set({ user, isAuthenticated: true, isLoading: false })
// 显示成功通知
get().addNotification({
type: 'success',
message: '登录成功',
})
} catch (error) {
const message = error instanceof Error ? error.message : '登录失败'
set({ error: message, isLoading: false })
// 显示错误通知
get().addNotification({
type: 'error',
message,
})
}
},
})
动态切片 #
按需加载切片 #
tsx
// store/dynamicStore.ts
import { create, StoreApi, UseBoundStore } from 'zustand'
interface DynamicSlice {
loaded: boolean
loadSlice: <T extends object>(name: string, slice: StateCreator<T>) => void
}
let store: UseBoundStore<StoreApi<any>>
export const getStore = () => {
if (!store) {
store = create<DynamicSlice>((set, get) => ({
loaded: false,
loadSlice: (name, slice) => {
const currentState = get()
if (!(name in currentState)) {
set({ [name]: slice(set as any, get as any, store as any) })
}
},
}))
}
return store
}
// 使用
async function loadUserSlice() {
const { createUserSlice } = await import('./slices/userSlice')
getStore().getState().loadSlice('user', createUserSlice)
}
条件切片 #
tsx
// store/conditionalStore.ts
import { create } from 'zustand'
interface BaseSlice {
initialized: boolean
initialize: () => void
}
interface FeatureSlice {
featureEnabled: boolean
enableFeature: () => void
}
type StoreState = BaseSlice & Partial<FeatureSlice>
export const useStore = create<StoreState>((set, get) => ({
initialized: false,
initialize: () => {
set({ initialized: true })
// 根据条件加载功能切片
if (someCondition) {
import('./slices/featureSlice').then(({ createFeatureSlice }) => {
set(createFeatureSlice(set as any, get as any, useStore as any))
})
}
},
}))
切片工厂 #
创建可复用切片 #
tsx
// factories/createCrudSlice.ts
import { StateCreator } from 'zustand'
interface CrudSlice<T extends { id: string }> {
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
clearItems: () => void
}
export function createCrudSlice<T extends { id: string }>(
name: string,
initialItems: T[] = []
): StateCreator<CrudSlice<T>> {
return (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),
selectedItem: state.selectedItem?.id === id ? null : state.selectedItem,
})),
selectItem: (item) => set({ selectedItem: item }),
clearItems: () => set({ items: [], selectedItem: null }),
})
}
// 使用
interface Todo {
id: string
text: string
completed: boolean
}
const createTodoSlice = createCrudSlice<Todo>('todos')
interface Post {
id: string
title: string
content: string
}
const createPostSlice = createCrudSlice<Post>('posts')
项目结构 #
推荐目录结构 #
text
src/
├── store/
│ ├── index.ts # 主 store 导出
│ ├── slices/
│ │ ├── userSlice.ts # 用户切片
│ │ ├── cartSlice.ts # 购物车切片
│ │ ├── themeSlice.ts # 主题切片
│ │ └── index.ts # 切片导出
│ ├── middleware/
│ │ ├── logger.ts # 日志中间件
│ │ └── index.ts
│ └── types/
│ └── slices.ts # 切片类型定义
├── hooks/
│ ├── useUser.ts # 用户相关 hooks
│ ├── useCart.ts # 购物车相关 hooks
│ └── useTheme.ts # 主题相关 hooks
└── components/
└── ...
切片导出 #
tsx
// store/slices/index.ts
export type { UserSlice, User } from './userSlice'
export { createUserSlice } from './userSlice'
export type { CartSlice, CartItem } from './cartSlice'
export { createCartSlice } from './cartSlice'
export type { ThemeSlice, Theme } from './themeSlice'
export { createThemeSlice } from './themeSlice'
自定义 Hooks #
tsx
// hooks/useUser.ts
import { useStore } from '../store'
export function useUser() {
const user = useStore((state) => state.user)
const isAuthenticated = useStore((state) => state.isAuthenticated)
const isLoading = useStore((state) => state.isLoading)
const error = useStore((state) => state.error)
const login = useStore((state) => state.login)
const logout = useStore((state) => state.logout)
const updateUser = useStore((state) => state.updateUser)
return {
user,
isAuthenticated,
isLoading,
error,
login,
logout,
updateUser,
}
}
// 使用
function UserProfile() {
const { user, isAuthenticated, logout } = useUser()
if (!isAuthenticated) {
return <LoginPrompt />
}
return (
<div>
<h1>{user?.name}</h1>
<button onClick={logout}>Logout</button>
</div>
)
}
最佳实践 #
1. 保持切片独立 #
tsx
// ✅ 好:切片独立,无直接依赖
const createUserSlice = (set) => ({
user: null,
setUser: (user) => set({ user }),
})
// ❌ 不好:切片直接依赖其他切片
const createUserSlice = (set, get) => ({
user: null,
setUser: (user) => {
set({ user })
// 直接调用其他切片的方法
get().updateCart({ userId: user.id })
},
})
2. 使用类型安全的组合 #
tsx
// ✅ 好:显式类型定义
type StoreState = UserSlice & CartSlice & ThemeSlice
const useStore = create<StoreState>()((...a) => ({
...createUserSlice(...a),
...createCartSlice(...a),
...createThemeSlice(...a),
}))
3. 封装选择器 #
tsx
// hooks/useSelectors.ts
import { shallow } from 'zustand/shallow'
export function useUserSelectors() {
return useStore(
(state) => ({
user: state.user,
isAuthenticated: state.isAuthenticated,
}),
shallow
)
}
总结 #
切片模式的关键点:
- 将大型 Store 拆分成独立的切片
- 每个切片负责一个特定领域
- 使用类型安全的组合方式
- 通过
get函数实现切片间通信 - 使用工厂函数创建可复用切片
接下来,让我们学习 计算属性,掌握派生状态处理技巧。
最后更新:2026-03-28