memo ti permette di saltare la ri-renderizzazione di un componente quando le sue props non sono cambiate.
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)- Reference
- Usage
- Saltare la ri-renderizzazione quando le props non cambiano
- Aggiornare un componente memorizzato usando lo state
- Aggiornare un componente memorizzato usando un context
- Minimizzare i cambiamenti delle props
- Specificare una funzione di confronto personalizzata
- Ho ancora bisogno di React.memo se uso React Compiler?
- Troubleshooting
Reference
memo(Component, arePropsEqual?)
Avvolgi un componente in memo per ottenere una versione memorizzata di quel componente. Questa versione memorizzata del tuo componente di solito non verrà ri-renderizzata quando il componente genitore viene ri-renderizzato, finché le sue props non sono cambiate. Ma React potrebbe comunque ri-renderizzarlo: la memorizzazione è un’ottimizzazione delle prestazioni, non una garanzia.
import { memo } from 'react';
const SomeComponent = memo(function SomeComponent(props) {
// ...
});Parameters
-
Component: Il componente che vuoi memorizzare.memonon modifica questo componente, ma restituisce al suo posto un nuovo componente memorizzato. È accettato qualsiasi componente React valido, incluse funzioni e componentiforwardRef. -
optional
arePropsEqual: Una funzione che accetta due argomenti: le props precedenti del componente e le sue nuove props. Dovrebbe restituiretruese le props vecchie e nuove sono uguali: cioè, se il componente renderizzerà lo stesso output e si comporterà allo stesso modo con le nuove props come con le vecchie. Altrimenti dovrebbe restituirefalse. Di solito non specificherai questa funzione. Per impostazione predefinita, React confronterà ogni prop conObject.is.
Returns
memo restituisce un nuovo componente React. Si comporta come il componente passato a memo, tranne che React non lo ri-renderizzerà sempre quando il genitore viene ri-renderizzato, a meno che le sue props non siano cambiate.
Usage
Saltare la ri-renderizzazione quando le props non cambiano
Di norma React ri-renderizza un componente ogni volta che il genitore viene ri-renderizzato. Con memo, puoi creare un componente che React non ri-renderizzerà quando il genitore viene ri-renderizzato, purché le nuove props siano le stesse delle vecchie. Un componente del genere si dice memorizzato.
Per memorizzare un componente, avvolgilo in memo e usa il valore che restituisce al posto del componente originale:
const Greeting = memo(function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
});
export default Greeting;Un componente React dovrebbe sempre avere logica di renderizzazione pura. Ciò significa che deve restituire lo stesso output se props, state e context non sono cambiati. Usando memo, stai dicendo a React che il tuo componente rispetta questo requisito, quindi React non ha bisogno di ri-renderizzarlo finché le sue props non sono cambiate. Anche con memo, il tuo componente verrà ri-renderizzato se cambia il suo state o se cambia un context che sta usando.
In questo esempio, nota che il componente Greeting viene ri-renderizzato ogni volta che name cambia (perché è una delle sue props), ma non quando cambia address (perché non viene passato a Greeting come prop):
import { memo, useState } from 'react'; export default function MyApp() { const [name, setName] = useState(''); const [address, setAddress] = useState(''); return ( <> <label> Nome{': '} <input value={name} onChange={e => setName(e.target.value)} /> </label> <label> Indirizzo{': '} <input value={address} onChange={e => setAddress(e.target.value)} /> </label> <Greeting name={name} /> </> ); } const Greeting = memo(function Greeting({ name }) { console.log("Greeting was rendered at", new Date().toLocaleTimeString()); return <h3>Hello{name && ', '}{name}!</h3>; });
Approfondimento
Se la tua app è come questo sito, e la maggior parte delle interazioni è grossolana (come sostituire una pagina o un’intera sezione), la memorizzazione di solito non è necessaria. D’altra parte, se la tua app è più simile a un editor di disegno, e la maggior parte delle interazioni è granulare (come spostare forme), potresti trovare la memorizzazione molto utile.
Ottimizzare con memo ha valore solo quando il tuo componente viene ri-renderizzato spesso con le stesse identiche props, e la sua logica di ri-renderizzazione è costosa. Se non c’è lag percettibile quando il tuo componente viene ri-renderizzato, memo non è necessario. Tieni presente che memo è completamente inutile se le props passate al tuo componente sono sempre diverse, ad esempio se passi un oggetto o una funzione semplice definita durante la renderizzazione. Per questo motivo spesso avrai bisogno di useMemo e useCallback insieme a memo.
Non c’è alcun beneficio nell’avvolgere un componente in memo in altri casi. Non c’è nemmeno un danno significativo nel farlo, quindi alcuni team scelgono di non pensare ai casi individuali e di memorizzare il più possibile. Lo svantaggio di questo approccio è che il codice diventa meno leggibile. Inoltre, non tutta la memorizzazione è efficace: un singolo valore che è “sempre nuovo” basta a rompere la memorizzazione per un intero componente.
In pratica, puoi evitare molta memorizzazione seguendo alcuni principi:
- Quando un componente avvolge visivamente altri componenti, accetta JSX come children. In questo modo, quando il componente wrapper aggiorna il proprio state, React sa che i suoi figli non hanno bisogno di ri-renderizzarsi.
- Preferisci lo state locale e non sollevare lo state più in alto del necessario. Per esempio, non tenere lo state transitorio come form e se un elemento è in hover in cima all’albero o in una libreria di state globale.
- Mantieni la tua logica di renderizzazione pura. Se ri-renderizzare un componente causa un problema o produce un artefatto visivo evidente, è un bug nel tuo componente! Correggi il bug invece di aggiungere memorizzazione.
- Evita Effetti non necessari che aggiornano lo state. La maggior parte dei problemi di prestazioni nelle app React è causata da catene di aggiornamenti originati da Effetti che fanno renderizzare di nuovo i componenti più e più volte.
- Prova a rimuovere dipendenze non necessarie dai tuoi Effetti. Per esempio, invece della memorizzazione, spesso è più semplice spostare un oggetto o una funzione dentro un Effetto o fuori dal componente.
Se un’interazione specifica sembra ancora lenta, usa il profiler di React Developer Tools per vedere quali componenti trarrebbero maggior beneficio dalla memorizzazione, e aggiungi memorizzazione dove serve. Questi principi rendono i tuoi componenti più facili da debuggare e capire, quindi è comunque utile seguirli. A lungo termine, stiamo ricercando come fare memorizzazione granulare automaticamente per risolvere il problema una volta per tutte.
Aggiornare un componente memorizzato usando lo state
Anche quando un componente è memorizzato, verrà comunque ri-renderizzato quando cambia il suo state. La memorizzazione riguarda solo le props passate al componente dal genitore.
import { memo, useState } from 'react'; export default function MyApp() { const [name, setName] = useState(''); const [address, setAddress] = useState(''); return ( <> <label> Nome{': '} <input value={name} onChange={e => setName(e.target.value)} /> </label> <label> Indirizzo{': '} <input value={address} onChange={e => setAddress(e.target.value)} /> </label> <Greeting name={name} /> </> ); } const Greeting = memo(function Greeting({ name }) { console.log('Greeting was rendered at', new Date().toLocaleTimeString()); const [greeting, setGreeting] = useState('Hello'); return ( <> <h3>{greeting}{name && ', '}{name}!</h3> <GreetingSelector value={greeting} onChange={setGreeting} /> </> ); }); function GreetingSelector({ value, onChange }) { return ( <> <label> <input type="radio" checked={value === 'Hello'} onChange={e => onChange('Hello')} /> Saluto normale </label> <label> <input type="radio" checked={value === 'Hello and welcome'} onChange={e => onChange('Hello and welcome')} /> Saluto entusiasta </label> </> ); }
Se imposti una variabile di state al suo valore attuale, React salterà la ri-renderizzazione del tuo componente anche senza memo. Potresti comunque vedere la funzione del tuo componente chiamata un’ulteriore volta, ma il risultato verrà scartato.
Aggiornare un componente memorizzato usando un context
Anche quando un componente è memorizzato, verrà comunque ri-renderizzato quando cambia un context che sta usando. La memorizzazione riguarda solo le props passate al componente dal genitore.
import { createContext, memo, useContext, useState } from 'react'; const ThemeContext = createContext(null); export default function MyApp() { const [theme, setTheme] = useState('dark'); function handleClick() { setTheme(theme === 'dark' ? 'light' : 'dark'); } return ( <ThemeContext value={theme}> <button onClick={handleClick}> Cambia tema </button> <Greeting name="Taylor" /> </ThemeContext> ); } const Greeting = memo(function Greeting({ name }) { console.log("Greeting was rendered at", new Date().toLocaleTimeString()); const theme = useContext(ThemeContext); return ( <h3 className={theme}>Hello, {name}!</h3> ); });
Per far ri-renderizzare il tuo componente solo quando cambia una parte di un context, dividi il componente in due. Leggi ciò che ti serve dal context nel componente esterno, e passalo a un figlio memorizzato come prop.
Minimizzare i cambiamenti delle props
Quando usi memo, il tuo componente viene ri-renderizzato ogni volta che una prop non è superficialmente uguale a quella precedente. Ciò significa che React confronta ogni prop del tuo componente con il suo valore precedente usando il confronto Object.is. Nota che Object.is(3, 3) è true, ma Object.is({}, {}) è false.
Per ottenere il massimo da memo, minimizza le volte in cui le props cambiano. Per esempio, se la prop è un oggetto, evita che il componente genitore ricrei quell’oggetto ogni volta usando useMemo:
function Page() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
const person = useMemo(
() => ({ name, age }),
[name, age]
);
return <Profile person={person} />;
}
const Profile = memo(function Profile({ person }) {
// ...
});Un modo migliore per minimizzare i cambiamenti delle props è assicurarsi che il componente accetti nelle props solo le informazioni minime necessarie. Per esempio, potrebbe accettare valori individuali invece di un intero oggetto:
function Page() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
return <Profile name={name} age={age} />;
}
const Profile = memo(function Profile({ name, age }) {
// ...
});Anche i valori individuali a volte possono essere proiettati su altri che cambiano meno frequentemente. Per esempio, qui un componente accetta un booleano che indica la presenza di un valore piuttosto che il valore stesso:
function GroupsLanding({ person }) {
const hasGroups = person.groups !== null;
return <CallToAction hasGroups={hasGroups} />;
}
const CallToAction = memo(function CallToAction({ hasGroups }) {
// ...
});Quando devi passare una funzione a un componente memorizzato, dichiara la funzione fuori dal tuo componente in modo che non cambi mai, oppure usa useCallback per memorizzare la sua definizione tra le ri-renderizzazioni.
Specificare una funzione di confronto personalizzata
In casi rari può essere impraticabile minimizzare i cambiamenti delle props di un componente memorizzato. In quel caso, puoi fornire una funzione di confronto personalizzata, che React userà per confrontare le props vecchie e nuove invece di usare l’uguaglianza superficiale. Questa funzione viene passata come secondo argomento a memo. Dovrebbe restituire true solo se le nuove props produrrebbero lo stesso output delle vecchie props; altrimenti dovrebbe restituire false.
const Chart = memo(function Chart({ dataPoints }) {
// ...
}, arePropsEqual);
function arePropsEqual(oldProps, newProps) {
return (
oldProps.dataPoints.length === newProps.dataPoints.length &&
oldProps.dataPoints.every((oldPoint, index) => {
const newPoint = newProps.dataPoints[index];
return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y;
})
);
}Se lo fai, usa il pannello Performance negli strumenti per sviluppatori del browser per assicurarti che la tua funzione di confronto sia effettivamente più veloce della ri-renderizzazione del componente. Potresti rimanere sorpreso.
Quando fai misurazioni delle prestazioni, assicurati che React sia in esecuzione in modalità produzione.
Ho ancora bisogno di React.memo se uso React Compiler?
Quando abiliti React Compiler, di solito non hai più bisogno di React.memo. Il compiler ottimizza automaticamente la ri-renderizzazione dei componenti per te.
Ecco come funziona:
Senza React Compiler, hai bisogno di React.memo per evitare ri-renderizzazioni non necessarie:
// Il genitore si ri-renderizza ogni secondo
function Parent() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<>
<h1>Seconds: {seconds}</h1>
<ExpensiveChild name="John" />
</>
);
}
// Senza memo, questo si ri-renderizza ogni secondo anche se le props non cambiano
const ExpensiveChild = memo(function ExpensiveChild({ name }) {
console.log('ExpensiveChild rendered');
return <div>Hello, {name}!</div>;
});Con React Compiler abilitato, la stessa ottimizzazione avviene automaticamente:
// Nessun memo necessario: il compiler previene le ri-renderizzazioni automaticamente
function ExpensiveChild({ name }) {
console.log('ExpensiveChild rendered');
return <div>Hello, {name}!</div>;
}Ecco la parte chiave di ciò che genera React Compiler:
function Parent() {
const $ = _c(7);
const [seconds, setSeconds] = useState(0);
// ... altro codice ...
let t3;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <ExpensiveChild name="John" />;
$[4] = t3;
} else {
t3 = $[4];
}
// ... istruzione return ...
}Nota le righe evidenziate: il compiler avvolge <ExpensiveChild name="John" /> in un controllo della cache. Poiché la prop name è sempre "John", questo JSX viene creato una volta e riusato a ogni ri-renderizzazione del genitore. Questo è esattamente ciò che fa React.memo: impedisce al figlio di ri-renderizzarsi quando le sue props non sono cambiate.
React Compiler automaticamente:
- Traccia che la prop
namepassata aExpensiveChildnon è cambiata - Riutilizza il JSX creato in precedenza per
<ExpensiveChild name="John" /> - Salta completamente la ri-renderizzazione di
ExpensiveChild
Ciò significa che puoi rimuovere React.memo dai tuoi componenti in sicurezza quando usi React Compiler. Il compiler fornisce la stessa ottimizzazione automaticamente, rendendo il tuo codice più pulito e più facile da mantenere.
Troubleshooting
Il mio componente si ri-renderizza quando una prop è un oggetto, un array o una funzione
React confronta le props vecchie e nuove per uguaglianza superficiale: cioè, considera se ogni nuova prop è reference-equal alla prop vecchia. Se crei un nuovo oggetto o array ogni volta che il genitore viene ri-renderizzato, anche se i singoli elementi sono gli stessi, React lo considererà comunque cambiato. Allo stesso modo, se crei una nuova funzione quando renderizzi il componente genitore, React la considererà cambiata anche se la funzione ha la stessa definizione. Per evitare questo, semplifica le props o memorizza le props nel componente genitore.