React
React.memo: quando serve davvero
React.memo salta il re-render quando le props restano uguali, ma paga solo su componenti costosi con props stabili: altrove aggiunge confronti e memoria senza guadagno.
In sintesi
- React.memo confronta ogni prop con Object.is e salta il render quando tutte coincidono.
- Un oggetto o una funzione creata nel render del genitore annulla il confronto a ogni passaggio.
- memo non ferma i re-render causati da stato interno o da un context che cambia.
React.memo avvolge un componente e salta il render quando le props sono uguali. Il confronto è superficiale, quindi paga su componenti costosi con props primitive e stabili, e altrove aggiunge lavoro.
Cosa confronta davvero
Il confronto è Object.is su ogni prop, una per una. Numeri, stringhe e booleani uguali fermano il render; un oggetto creato nel render del genitore ha una nuova identità a ogni passaggio, quindi il confronto fallisce sempre e il componente si ri-renderizza comunque.
Il caso in cui paga
Una tabella con cinquanta righe e un campo di ricerca sopra: ogni tasto premuto ri-renderizza il genitore. Se la riga è memoizzata e riceve solo id e label, le righe che non cambiano non vengono ricalcolate.
const Row = memo(function Row({ id, label, onSelect }) {
return (
onSelect(id)}>
{label}
);
});
La funzione onSelect però è una prop come le altre: se nasce nel render del genitore cambia identità a ogni giro e annulla il memo. Va stabilizzata con useCallback, oppure passata da un context che cambia raramente.
Il caso in cui non paga
- Props non stabili: oggetti, array e funzioni creati nel render sconfiggono il confronto, e il memo diventa costo puro.
- Componenti economici: una cella con una stringa costa meno del confronto e della memoria che il memo richiede.
- Contesto:
memonon ferma i re-render causati da un context che cambia; quelli passano comunque.
Misurarlo prima di aggiungerlo
npm create vite@latest memo-demo -- --template react-ts
cd memo-demo
npm install
npm run dev
Con l’app aperta, il Profiler di React DevTools registra i render per componente e mostra quanto dura ognuno. Aggiungi memo solo al componente che compare in cima alla lista con un tempo misurabile, non a tutti quelli che sembrano lenti.
Record vivi nel catalogo
Due record adatti al test: table per una griglia con molte righe e list-item per una riga riutilizzabile. Il catalogo completo è tra i 111.985 componenti e le 12.808 icone.
La limitazione
memo ferma solo i render che arrivano dal genitore. Uno stato interno che cambia, un context che cambia o una prop non stabile ri-renderizzano il componente lo stesso; e il confronto non è gratis, quindi sui componenti piccoli il rimedio costa più del problema.
Dove continuare
La funzione stabile che serve al memo è in useCallback; il caso in cui il memo non aiuta è in context e performance; il confronto con un framework senza memo manuale è in Svelte vs React.
Domande frequenti
React.memo è la stessa cosa di useMemo?
No. memo avvolge un componente e salta il suo render quando le props non cambiano; useMemo conserva il risultato di un calcolo dentro un componente. Servono a due problemi diversi.
Quando il confronto superficiale non basta?
Quando una prop è un oggetto con molti campi e il genitore non può stabilizzarla. In quel caso si passa una funzione di confronto a memo, ma un confronto profondo costa quanto il render che vuoi evitare.
memo funziona con i componenti di classe?
Sì. memo accetta qualsiasi componente, di classe o funzione, e restituisce un componente con il confronto sulle props. Non funziona invece sui valori: per quelli c’è useMemo.
Serve ancora memo con il compilatore di React?
Molto meno. Il compilatore inserisce la memoizzazione automatica dove riconosce un guadagno, quindi il memo scritto a mano resta utile solo dopo una misura che mostra un componente ancora lento.
Perché il mio componente memo continua a ri-renderizzarsi?
Quasi sempre per una prop con identità nuova a ogni render, oppure per un context che cambia. Controlla le props con il Profiler e verifica quali valori vengono ricreati nel genitore.