TypeScript
Tipizzare lo Store con create
La funzione create accetta un parametro generico per definire il tipo dello stato. Nota la doppia invocazione create<T>()((set) => ...) necessaria per l’inferenza corretta dei middleware.
import { create } from 'zustand'
interface BearState {
bears: number
increase: (by: number) => void
reset: () => void
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
reset: () => set({ bears: 0 }),
}))
TypeScript verifichera’ che l’oggetto restituito dalla funzione corrisponda esattamente a BearState.
Inferire i Tipi dallo Store
Puoi estrarre il tipo dello stato direttamente dallo store creato.
// Tipo inferito dall'intero store
type BearStoreState = ReturnType<typeof useBearStore.getState>
// Equivalente a BearState, utile se non hai definito l'interfaccia separatamente
Questo e’ particolarmente utile quando costruisci utility generiche.
function useStoreField<T>(
store: { getState: () => T; subscribe: (fn: () => void) => () => void },
selector: (state: T) => unknown
) {
// ...
}
StateCreator Tipizzato
Per il pattern slices, StateCreator accetta fino a 4 parametri generici.
import { StateCreator } from 'zustand'
// StateCreator<SliceType, MiddlewareTypes, MiddlewareTypes, SliceType>
interface AuthSlice {
user: string | null
login: (user: string) => void
}
interface CartSlice {
items: string[]
addItem: (item: string) => void
}
type AppStore = AuthSlice & CartSlice
// Slice con accesso all'intero store
const createAuthSlice: StateCreator<AppStore, [], [], AuthSlice> = (set) => ({
user: null,
login: (user) => set({ user }),
})
const createCartSlice: StateCreator<AppStore, [], [], CartSlice> = (set) => ({
items: [],
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
})
Middleware Tipizzati
Quando usi middleware, il tipo va specificato nei parametri generici di StateCreator.
import { StateCreator } from 'zustand'
import { PersistOptions } from 'zustand/middleware'
interface TodoSlice {
todos: { id: string; text: string }[]
addTodo: (text: string) => void
}
// Con persist middleware
const createTodoSlice: StateCreator<
TodoSlice,
[['zustand/persist', unknown]],
[],
TodoSlice
> = (set) => ({
todos: [],
addTodo: (text) =>
set((s) => ({
todos: [...s.todos, { id: crypto.randomUUID(), text }],
})),
})
Combine Helper
Il helper combine inferisce automaticamente il tipo dallo stato iniziale, eliminando la necessita’ di definire un’interfaccia esplicita.
import { create } from 'zustand'
import { combine } from 'zustand/middleware'
const useStore = create(
combine(
// Stato iniziale: il tipo viene inferito
{ count: 0, name: 'Zustand' },
// Le azioni ricevono set e get gia' tipizzati
(set, get) => ({
increment: () => set({ count: get().count + 1 }),
updateName: (name: string) => set({ name }),
})
)
)
// useStore.getState().count -> number (inferito)
// useStore.getState().name -> string (inferito)
combine e’ ideale per store piccoli dove non vuoi creare interfacce separate.
Generic Stores
Puoi creare factory function per store riutilizzabili con tipi generici.
interface CrudState<T extends { id: string }> {
items: T[]
addItem: (item: T) => void
removeItem: (id: string) => void
updateItem: (id: string, updates: Partial<T>) => void
}
function createCrudStore<T extends { id: string }>() {
return create<CrudState<T>>()((set) => ({
items: [],
addItem: (item) =>
set((s) => ({ items: [...s.items, item] })),
removeItem: (id) =>
set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
updateItem: (id, updates) =>
set((s) => ({
items: s.items.map((i) =>
i.id === id ? { ...i, ...updates } : i
),
})),
}))
}
// Utilizzo
interface Product { id: string; name: string; price: number }
const useProductStore = createCrudStore<Product>()
interface User { id: string; email: string; role: string }
const useUserStore = createCrudStore<User>()
Questo pattern elimina la duplicazione di logica CRUD comune tra diversi store.