memo
permite que você evite a re-renderização de um componente quando suas props permanecem inalteradas.
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
Referência
memo(Component, arePropsEqual?)
Envolva um componente em memo
para obter uma versão memoizada desse componente. Essa versão memoizada do seu componente geralmente não será re-renderizada quando seu componente pai for re-renderizado, desde que suas props não tenham mudado. Mas o React ainda pode re-renderizá-lo: a memoização é uma otimização de desempenho, não uma garantia.
import { memo } from 'react';
const SomeComponent = memo(function SomeComponent(props) {
// ...
});
Parâmetros
-
Component
: O componente que você deseja memoizar. Omemo
não modifica este componente, mas retorna um novo componente memoizado. Qualquer componente React válido, incluindo funções e componentesforwardRef
, é aceito. -
opcional
arePropsEqual
: Uma função que aceita dois argumentos: as props anteriores do componente e suas novas props. Ela deve retornartrue
se as props antigas e novas forem iguais: ou seja, se o componente renderizar a mesma saída e se comportar da mesma forma com as novas props como com as antigas. Caso contrário, deve retornarfalse
. Normalmente, você não especificará essa função. Por padrão, o React comparará cada prop usandoObject.is
.
Retorna
memo
retorna um novo componente React. Ele se comporta da mesma forma que o componente fornecido ao memo
, exceto que o React não re-renderizará sempre que seu pai estiver sendo re-renderizado, a menos que suas props tenham mudado.
Uso
Ignorando re-renderizações quando as props estão inalteradas
O React normalmente re-renderiza um componente sempre que seu pai re-renderiza. Com memo
, você pode criar um componente que o React não re-renderizará quando seu pai re-renderizar, desde que suas novas props sejam as mesmas que as antigas. Um componente assim é dito ser memoizado.
Para memoizar um componente, enrole-o em memo
e use o valor que ele retorna em vez do seu componente original:
const Greeting = memo(function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
});
export default Greeting;
Um componente React deve sempre ter lógica de renderização pura. Isso significa que ele deve retornar a mesma saída se suas props, estado e contexto não tiverem mudado. Ao usar memo
, você está dizendo ao React que seu componente atende a esse requisito, então o React não precisa re-renderizar enquanto suas props não tiverem mudado. Mesmo com memo
, seu componente re-renderizará se seu próprio estado mudar ou se um contexto que ele está usando mudar.
Neste exemplo, note que o componente Greeting
re-renderiza sempre que name
é alterado (porque essa é uma de suas props), mas não quando address
é alterado (porque isso não é passado para Greeting
como uma prop):
import { memo, useState } from 'react'; export default function MyApp() { const [name, setName] = useState(''); const [address, setAddress] = useState(''); return ( <> <label> Name{': '} <input value={name} onChange={e => setName(e.target.value)} /> </label> <label> Address{': '} <input value={address} onChange={e => setAddress(e.target.value)} /> </label> <Greeting name={name} /> </> ); } const Greeting = memo(function Greeting({ name }) { console.log("Greeting foi renderizado em", new Date().toLocaleTimeString()); return <h3>Olá{name && ', '}{name}!</h3>; });
Deep Dive
Se seu aplicativo é como este site, e a maioria das interações são grosseiras (como substituir uma página ou uma seção inteira), a memoização geralmente é desnecessária. Por outro lado, se seu aplicativo é mais como um editor de desenho, e a maioria das interações é granular (como mover formas), então você pode achar a memoização muito útil.
Otimizar com memo
só é valioso quando seu componente re-renderiza frequentemente com as mesmas props exatas, e sua lógica de re-renderização é cara. Se não houver um atraso perceptível quando seu componente re-renderiza, memo
é desnecessário. Lembre-se de que memo
é completamente inútil se as props passadas para seu componente são sempre diferentes, como se você passasse um objeto ou uma função simples definida durante a renderização. É por isso que você frequentemente precisará de useMemo
e useCallback
juntamente com memo
.
Não há benefício em envolver um componente em memo
em outros casos. Não há dano significativo em fazer isso também, então algumas equipes escolhem não pensar em casos individuais e memoizar o máximo possível. O lado negativo dessa abordagem é que o código se torna menos legível. Além disso, nem toda memoização é eficaz: um único valor que é “sempre novo” é suficiente para quebrar a memoização de um componente inteiro.
Na prática, você pode tornar desnecessária muita memoização seguindo alguns princípios:
- Quando um componente envolve visualmente outros componentes, deixe-o aceitar JSX como filhos. Dessa forma, quando o componente envolvente atualiza seu próprio estado, o React sabe que seus filhos não precisam re-renderizar.
- Prefira estado local e não eleve o estado mais do que o necessário. Por exemplo, não mantenha estado transitório como formulários e se um item está sendo destacado no topo da sua árvore ou em uma biblioteca de estado global.
- Mantenha sua lógica de renderização pura. Se re-renderizar um componente causar um problema ou produzir algum artefato visual perceptível, é um bug no seu componente! Corrija o bug em vez de adicionar memoização.
- Evite Efeitos desnecessários que atualizam o estado. A maioria dos problemas de desempenho em aplicativos React são causados por cadeias de atualizações originadas a partir de Efeitos que fazem seus componentes renderizarem repetidamente.
- Tente remover dependências desnecessárias de seus Efeitos. Por exemplo, em vez de memoização, muitas vezes é mais simples mover algum objeto ou função dentro de um Efeito ou fora do componente.
Se uma interação específica ainda parecer lenta, use o perfilador de Ferramentas de Desenvolvedor do React para ver quais componentes se beneficiariam mais da memoização e adicione memoização onde necessário. Esses princípios tornam seus componentes mais fáceis de depurar e entender, então é bom segui-los em qualquer caso. A longo prazo, estamos pesquisando fazer memoização granular automaticamente para resolver isso de uma vez por todas.
Atualizando um componente memoizado usando estado
Mesmo quando um componente é memoizado, ele ainda re-renderiza quando seu próprio estado muda. A memoização só tem relação com as props que são passadas para o componente a partir do seu pai.
import { memo, useState } from 'react'; export default function MyApp() { const [name, setName] = useState(''); const [address, setAddress] = useState(''); return ( <> <label> Name{': '} <input value={name} onChange={e => setName(e.target.value)} /> </label> <label> Address{': '} <input value={address} onChange={e => setAddress(e.target.value)} /> </label> <Greeting name={name} /> </> ); } const Greeting = memo(function Greeting({ name }) { console.log('Greeting foi renderizado em', 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')} /> Saudação regular </label> <label> <input type="radio" checked={value === 'Hello and welcome'} onChange={e => onChange('Hello and welcome')} /> Saudação entusiástica </label> </> ); }
Se você definir uma variável de estado para seu valor atual, o React ignorará a re-renderização do seu componente mesmo sem memo
. Você ainda pode ver a função do seu componente sendo chamada uma vez a mais, mas o resultado será descartado.
Atualizando um componente memoizado usando um contexto
Mesmo quando um componente é memoizado, ele ainda re-renderiza quando um contexto que está usando muda. A memoização só tem a ver com as props que são passadas para o componente a partir do seu pai.
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.Provider value={theme}> <button onClick={handleClick}> Trocar tema </button> <Greeting name="Taylor" /> </ThemeContext.Provider> ); } const Greeting = memo(function Greeting({ name }) { console.log("Greeting foi renderizado em", new Date().toLocaleTimeString()); const theme = useContext(ThemeContext); return ( <h3 className={theme}>Olá, {name}!</h3> ); });
Para fazer seu componente re-renderizar apenas quando parte de algum contexto mudar, divida seu componente em dois. Leia o que você precisa do contexto no componente externo e passe-o para um filho memoizado como uma prop.
Minimizar mudanças nas props
Quando você usa memo
, seu componente re-renderiza sempre que qualquer prop não é shallowly equal ao que era anteriormente. Isso significa que o React compara cada prop em seu componente com seu valor anterior usando a comparação Object.is
. Note que Object.is(3, 3)
é true
, mas Object.is({}, {})
é false
.
Para aproveitar ao máximo memo
, minimize as vezes que as props mudam. Por exemplo, se a prop for um objeto, impeça o componente pai de recriar esse objeto toda vez que ele re-renderizar 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 }) {
// ...
});
Uma maneira melhor de minimizar mudanças nas props é garantir que o componente aceite as informações mínimas necessárias em suas props. Por exemplo, ele poderia aceitar valores individuais em vez de um objeto inteiro:
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 }) {
// ...
});
Mesmo valores individuais podem às vezes ser projetados para aqueles que mudam com menos frequência. Por exemplo, aqui um componente aceita um booleano que indica a presença de um valor em vez do valor em si:
function GroupsLanding({ person }) {
const hasGroups = person.groups !== null;
return <CallToAction hasGroups={hasGroups} />;
}
const CallToAction = memo(function CallToAction({ hasGroups }) {
// ...
});
Quando você precisa passar uma função para um componente memoizado, declare-a fora do seu componente para que ela nunca mude, ou use useCallback
para armazenar sua definição entre re-renderizações.
Especificando uma função de comparação personalizada
Em casos raros, pode ser inviável minimizar as mudanças nas props de um componente memoizado. Nesse caso, você pode fornecer uma função de comparação personalizada, que o React usará para comparar as props antigas e novas em vez de usar igualdade superficial. Essa função é passada como um segundo argumento para memo
. Ela deve retornar true
apenas se as novas props resultarem na mesma saída que as props antigas; caso contrário, deve retornar 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 você fizer isso, use o painel de Desempenho nas ferramentas de desenvolvedor do seu navegador para ter certeza de que sua função de comparação é realmente mais rápida do que re-renderizar o componente. Você pode ficar surpreso.
Quando você fizer medições de desempenho, certifique-se de que o React esteja sendo executado no modo produção.
Resolução de Problemas
Meu componente re-renderiza quando uma prop é um objeto, array ou função
O React compara antigas e novas props por igualdade superficial: ou seja, considera se cada nova prop é igual em referência à antiga. Se você criar um novo objeto ou array toda vez que o pai re-renderizar, mesmo que os elementos individuais sejam os mesmos, o React ainda considerará que ele foi alterado. Da mesma forma, se você criar uma nova função ao renderizar o componente pai, o React considerará que ela mudou mesmo que a função tenha a mesma definição. Para evitar isso, simplifique as props ou memoize as props no componente pai.