Slices Pattern
Perché gli Slices
Quando lo store cresce, mantenere tutto in un unico file diventa ingestibile. Lo slices pattern permette di dividere lo stato in moduli separati che vengono poi combinati in un unico store.
StateCreator Type
Zustand espone il tipo StateCreator che definisce la firma di una “fetta” di store.
import { StateCreator } from 'zustand'
interface AuthSlice {
user: string | null
token: string | null
login: (user: string, token: string) => void
logout: () => void
}
const createAuthSlice: StateCreator<AuthSlice> = (set) => ({
user: null,
token: null,
login: (user, token) => set({ user, token }),
logout: () => set({ user: null, token: null }),
})
Definire Piu’ Slices
interface CartSlice {
items: { id: string; name: string; qty: number }[]
addItem: (item: { id: string; name: string }) => void
clearCart: () => void
}
const createCartSlice: StateCreator<CartSlice> = (set) => ({
items: [],
addItem: (item) =>
set((state) => {
const existing = state.items.find((i) => i.id === item.id)
if (existing) {
return {
items: state.items.map((i) =>
i.id === item.id ? { ...i, qty: i.qty + 1 } : i
),
}
}
return { items: [...state.items, { ...item, qty: 1 }] }
}),
clearCart: () => set({ items: [] }),
})
Combinare gli Slices
Unisci tutti gli slices in un unico store usando lo spread operator.
import { create } from 'zustand'
type AppStore = AuthSlice & CartSlice
const useAppStore = create<AppStore>()((...args) => ({
...createAuthSlice(...args),
...createCartSlice(...args),
}))
Lo store risultante ha tutte le proprietà e le azioni di entrambi gli slices.
function Header() {
const user = useAppStore((s) => s.user)
const itemCount = useAppStore((s) => s.items.length)
return (
<header>
<span>{user ?? 'Ospite'}</span>
<span>Carrello: {itemCount}</span>
</header>
)
}
Slices che Accedono ad Altri Slices
Uno slice puo’ leggere o aggiornare lo stato di un altro slice. Specifica l’intero tipo dello store come secondo parametro generico di StateCreator.
interface NotificationSlice {
notifications: string[]
notify: (msg: string) => void
}
// Il secondo generico e' lo store completo, il terzo e' un eventuale middleware
const createCartSliceWithNotify: StateCreator<
CartSlice & NotificationSlice,
[],
[],
CartSlice
> = (set, get) => ({
items: [],
addItem: (item) => {
set((state) => ({
items: [...state.items, { ...item, qty: 1 }],
}))
// Accede allo slice notifications
get().notify(`${item.name} aggiunto al carrello`)
},
clearCart: () => set({ items: [] }),
})
Organizzazione del Codice
La struttura consigliata per progetto di medie/grandi dimensioni.
src/
stores/
slices/
authSlice.ts # StateCreator<AuthSlice>
cartSlice.ts # StateCreator<CartSlice>
uiSlice.ts # StateCreator<UISlice>
useAppStore.ts # create() che combina tutti gli slices
types.ts # tipi condivisi tra slices
File useAppStore.ts
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { createAuthSlice, AuthSlice } from './slices/authSlice'
import { createCartSlice, CartSlice } from './slices/cartSlice'
import { createUISlice, UISlice } from './slices/uiSlice'
export type AppStore = AuthSlice & CartSlice & UISlice
export const useAppStore = create<AppStore>()(
devtools((...args) => ({
...createAuthSlice(...args),
...createCartSlice(...args),
...createUISlice(...args),
}))
)
Ogni slice e’ testabile in isolamento, facile da navigare e con responsabilita’ chiare. Quando lo store diventa complesso, questo pattern mantiene il codice organizzato e manutenibile.