00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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.