Selettori e Performance
Perché i Selettori Contano
Quando un componente sottoscrive l’intero store, viene ri-renderizzato ad ogni cambiamento di stato, anche se la parte che usa non è cambiata. I selettori risolvono questo problema.
// BAD: ri-renderizza ad ogni cambio dello store
const state = useStore()
// GOOD: ri-renderizza solo quando 'count' cambia
const count = useStore((state) => state.count)
Selettori Atomici
Seleziona un singolo valore alla volta. Ogni selettore crea una sottoscrizione indipendente.
function UserProfile() {
const name = useUserStore((s) => s.name)
const email = useUserStore((s) => s.email)
return (
<div>
<p>{name}</p>
<p>{email}</p>
</div>
)
}
Il componente si aggiorna solo se name o email cambiano. Cambiamenti ad altre proprietà dello store vengono ignorati.
Selettori Composti con useShallow
Quando vuoi estrarre più valori in un unico oggetto, Zustand confronta il risultato con Object.is per default. Un nuovo oggetto {} viene creato ad ogni render, causando re-render inutili.
useShallow risolve confrontando le proprietà in modo shallow (superficiale).
import { useShallow } from 'zustand/react/shallow'
function UserProfile() {
const { name, email, avatar } = useUserStore(
useShallow((s) => ({
name: s.name,
email: s.email,
avatar: s.avatar,
}))
)
return (
<div>
<img src={avatar} alt={name} />
<p>{name} - {email}</p>
</div>
)
}
useShallow con Array
Funziona anche restituendo un array invece di un oggetto.
const [name, email] = useUserStore(
useShallow((s) => [s.name, s.email])
)
Comparazione Custom
Puoi passare una funzione di uguaglianza come secondo argomento dell’hook per controllare manualmente quando ri-renderizzare.
import { shallow } from 'zustand/shallow'
const user = useUserStore(
(s) => ({ name: s.name, age: s.age }),
shallow // confronto shallow esplicito
)
Comparazione Personalizzata
const total = useCartStore(
(s) => s.items.reduce((sum, item) => sum + item.price, 0),
(prev, next) => Math.abs(prev - next) < 0.01 // tolleranza per float
)
Memorizzazione con useCallback
Se il selettore è una funzione inline che dipende da props, il riferimento cambia ad ogni render. Usa useCallback per stabilizzarlo.
import { useCallback } from 'react'
function TodoItem({ id }: { id: string }) {
const selector = useCallback(
(state: TodoState) => state.todos.find((t) => t.id === id),
[id]
)
const todo = useTodoStore(selector)
if (!todo) return null
return <li>{todo.text}</li>
}
Riepilogo Performance
| Pattern | Re-render |
|---|---|
useStore() senza selettore |
Ad ogni cambio dello store |
useStore(s => s.field) |
Solo quando field cambia |
useStore(s => ({a, b})) senza shallow |
Ad ogni cambio (nuovo oggetto) |
useShallow(s => ({a, b})) |
Solo quando a o b cambiano |
| Comparazione custom | Secondo la tua logica |
Scegli il pattern adatto alla granularità del tuo componente. Per la maggior parte dei casi, selettori atomici o useShallow sono la scelta migliore.