memo permette a un componente di saltare il nuovo render quando le sue props sono uguali all'ultima volta. Racchiudi un componente in memo(...) e, quando il suo genitore si renderizza, React prima confronta ogni nuova prop con quella vecchia; se sono tutte uguali, riutilizza il risultato precedente invece di chiamare di nuovo il componente.
Scrivi nell'input: tiene il passo, e la Console mostra render SlowList una sola volta, dal primo rendering. Togli il wrapper memo( (e la sua parentesi di chiusura) e scrivi di nuovo: ora ogni tasto aspetta 150 ms per la lista e registra un rendering.
La sintassi
import { memo } from 'react';
const MemoizedComponent = memo(SomeComponent, arePropsEqual?);
SomeComponentè un qualsiasi function component. Lo stile abituale è racchiudere direttamente la definizione:const Card = memo(function Card(props) { ... }). Dare un nome alla funzione interna mantiene il nome in React DevTools e nei messaggi di errore.arePropsEqualè facoltativa e serve di rado (vedi sotto).- Il risultato è un componente che renderizzi come qualsiasi altro.
Per default React confronta le vecchie e le nuove props in modo superficiale: controlla ogni prop con Object.is. Stringhe, numeri e booleani si confrontano per valore, quindi title="Hi" è uguale a title="Hi". Oggetti, array e funzioni si confrontano per riferimento: due oggetti con lo stesso contenuto sono comunque oggetti diversi.
Nell'esempio qui sopra, items è dichiarato fuori dal componente, quindi è lo stesso array a ogni rendering, e il confronto passa.
Nuovi oggetti e funzioni vanificano memo
Tutto ciò che è scritto come letterale nel corpo del genitore viene creato di nuovo a ogni rendering: { name: 'Ada' }, [1, 2, 3], () => save(). Passane uno a un componente con memo e la prop non è mai uguale a quella precedente.
Ogni clic registra render Badge object prop ma mai la versione con stringa. Ci sono tre modi per sistemare il caso dell'oggetto:
- Passa valori primitivi.
name="Ada"invece diuser={{ name: 'Ada' }}quando al figlio servono solo uno o due campi. - Sposta le costanti fuori dal componente. Un oggetto che non cambia mai può vivere a livello di modulo, come
itemsnel primo esempio. - Mettilo in cache. Racchiudi gli oggetti e gli array costruiti da props o stato in useMemo, e le funzioni in useCallback.
children ha lo stesso problema. <Card><p>Hello</p></Card> crea un nuovo oggetto elemento <p> a ogni rendering, quindi un Card con memo che riceve children esegue ogni volta un nuovo render.
Cosa memo non ferma
memo guarda solo le props che arrivano dal genitore. Un componente memorizzato si renderizza comunque quando:
- cambia il suo stato, tramite il suo
useStateouseReducer; - cambia un context che legge, tramite
useContext.
Clicca Unrelated e Panel viene saltato. Cambia tema e si renderizza, perché legge il context. Clicca Like e si renderizza, perché è cambiato il suo stato. È il comportamento voluto: memo serve a saltare il lavoro quando nulla di ciò da cui il componente dipende è cambiato, e context e stato sono cose da cui dipende.
Una funzione di confronto personalizzata
Il secondo argomento sostituisce il confronto superficiale. Riceve le props precedenti e quelle successive e restituisce true quando devono essere trattate come uguali, il che significa che React salta il rendering.
const MemoChart = memo(Chart, (prev, next) => {
return (
prev.points.length === next.points.length &&
prev.points.every((p, i) => p === next.points[i]) &&
prev.color === next.color &&
prev.onSelect === next.onSelect
);
});
Ti serve di rado, ed è facile sbagliarla. Se la funzione ignora una prop, il componente continua a mostrare il vecchio valore di quella prop. Ignorare una prop funzione è il caso peggiore: il componente saltato continua a chiamare la vecchia funzione, che legge lo stato vecchio. Confronta ogni prop, e non fare confronti profondi su dati grandi, perché a quel punto il confronto costa più del rendering che fa risparmiare. Props stabili tramite useMemo e useCallback sono quasi sempre la soluzione migliore.
Verificare che memo funzioni
La verifica più rapida è quella usata in questa pagina: un console.log in cima al componente. Se stampa quando ti aspettavi che venisse saltato, registra anche le props e confrontale con quelle precedenti per trovare quella che è cambiata. In un progetto reale, React DevTools aiuta ancora di più: attiva "Highlight updates when components render" nelle sue impostazioni per vedere quali componenti lampeggiano a ogni interazione, e la scheda Profiler registra ogni rendering e, con l'impostazione corrispondente attiva, il motivo per cui ogni componente si è renderizzato.
Due cose sembrano un fallimento di memo ma non lo sono:
- Il primo rendering avviene sempre.
memosalta i rendering solo dopo che il componente si è renderizzato una volta con quelle props. - Le build di sviluppo possono renderizzare di più. Con StrictMode, in sviluppo React renderizza ogni componente due volte per individuare il codice impuro. L'anteprima qui gira come una build di produzione, quindi ogni riga di log qui sopra significa un rendering reale.
Quando usare memo
memo vale la pena quando un componente è lento da renderizzare e il suo genitore si renderizza spesso con le stesse props per lui: una lunga lista o un grafico accanto a un input, una barra laterale dentro una pagina che si aggiorna ogni secondo. Per componenti che si renderizzano rapidamente, o le cui props cambiano comunque ogni volta, aggiunge un confronto e non porta nulla. Prima di memorizzare, verifica se puoi invece ristrutturare, spostando lo stato verso il basso o passando JSX come children, come mostrato nella pagina sul rendering.
React Compiler 1.0, se il tuo progetto lo usa, memorizza automaticamente i componenti e le loro props al momento della build, il che rende superflua la maggior parte delle chiamate manuali a memo. Senza, memo funziona esattamente come descritto qui e resta comune nel codice React.
Domande frequenti
Cosa fa React.memo?
Restituisce una versione memorizzata di un componente. Quando il genitore si renderizza, React confronta le nuove props con quelle precedenti e salta il rendering del componente se ogni prop è uguale secondo Object.is.
Perché il mio componente con memo esegue ancora un nuovo render?
Di solito perché una prop è un nuovo oggetto, array o funzione creato durante il rendering del genitore, quindi non è mai uguale a quello precedente. Il componente si renderizza anche quando cambia il suo stato o quando cambia un context che legge, con o senza memo.
Qual è la differenza tra React.memo e useMemo?
memo racchiude un componente e ne salta il rendering. useMemo è un hook dentro un componente che mette in cache un valore calcolato. Spesso lavorano insieme: useMemo mantiene stabile una prop oggetto così memo può saltare il rendering.
Devo racchiudere ogni componente in memo?
No. La maggior parte dei rendering è leggera, e il confronto delle props ha un suo costo. Usalo per componenti lenti da renderizzare che ricevono spesso le stesse props.
memo funziona con children?
Raramente. Il JSX passato come children è un nuovo oggetto a ogni rendering, quindi un componente con memo che riceve children esegue quasi sempre un nuovo render.