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

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.