Middleware Immer
Perché Immer
Aggiornare stato annidato in modo immutabile richiede spread operator a ogni livello. Con oggetti complessi il codice diventa illeggibile. Immer ti permette di scrivere aggiornamenti “mutabili” che vengono convertiti automaticamente in aggiornamenti immutabili.
Installazione
npm install immer
Il middleware immer e’ incluso in Zustand, non serve installare pacchetti aggiuntivi oltre a immer stesso.
Configurazione
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface TodoState {
todos: { id: string; text: string; done: boolean }[]
addTodo: (text: string) => void
toggleTodo: (id: string) => void
}
const useTodoStore = create<TodoState>()(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
state.todos.push({ id: crypto.randomUUID(), text, done: false })
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id)
if (todo) todo.done = !todo.done
}),
}))
)
Nota come push e l’assegnazione diretta sostituiscono spread e map. Immer intercetta le mutazioni e produce un nuovo oggetto immutabile.
Aggiornamenti Nested
Immer brilla con stato profondamente annidato.
interface SettingsState {
config: {
theme: {
primary: string
secondary: string
fonts: { heading: string; body: string }
}
notifications: { email: boolean; push: boolean }
}
updateHeadingFont: (font: string) => void
toggleEmailNotification: () => void
}
const useSettingsStore = create<SettingsState>()(
immer((set) => ({
config: {
theme: {
primary: '#3b82f6',
secondary: '#10b981',
fonts: { heading: 'Inter', body: 'Roboto' },
},
notifications: { email: true, push: false },
},
updateHeadingFont: (font) =>
set((state) => {
state.config.theme.fonts.heading = font
}),
toggleEmailNotification: () =>
set((state) => {
state.config.notifications.email = !state.config.notifications.email
}),
}))
)
Senza immer, updateHeadingFont richiederebbe tre livelli di spread.
Combinare Immer con Altri Middleware
I middleware Zustand si compongono avvolgendo uno dentro l’altro. Immer deve essere il piu’ interno (il piu’ vicino alla funzione di creazione dello store).
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
const useStore = create<MyState>()(
devtools(
persist(
immer((set) => ({
// stato e azioni con sintassi mutabile
count: 0,
increment: () => set((state) => { state.count++ }),
})),
{ name: 'my-storage' }
)
)
)
L’ordine di composizione dall’esterno all’interno e’: devtools > persist > immer.
Quando Usare Immer
| Scenario | Immer? |
|---|---|
| Stato piatto (pochi campi primitivi) | No, spread e’ sufficiente |
| Stato annidato a 2+ livelli | Si, semplifica molto |
| Array di oggetti con aggiornamenti frequenti | Si, push/splice sono piu’ leggibili |
| Store molto semplice | No, aggiunge una dipendenza inutile |
| Team abituato a Redux Toolkit | Si, stessa esperienza di scrittura |
Immer aggiunge circa 6KB (gzipped) al bundle. Per store semplici con stato piatto, lo spread nativo e’ preferibile. Per stato complesso e annidato, immer migliora significativamente la leggibilita’ e riduce il rischio di errori.