NOTA: Este artigo é apenas uma tradução. A fonte dele está no rodapé.
Guia completo sobre re-renders do React. O guia explica o que são re-renders, o que é um re-render necessário e desnecessário, o que pode acionar um re-render de React component.
Também inclui os patterns mais importantes que podem ajudar a evitar re-renders e alguns anti-patterns que levam a re-renders desnecessários e, como resultado, baixo desempenho. Cada pattern e anti-pattern é acompanhado por um auxílio visual e um exemplo de código funcional.
O que é o re-render no React?
Ao falar sobre o desempenho do React, há dois estágios principais com os quais precisamos nos preocupar:
- initial render: acontece quando um component aparece pela primeira vez na tela
- re-render: segunda e qualquer renderização consecutiva de um componente que já está na tela
Re-render acontece quando o React precisa atualizar o app com alguns dados novos. Geralmente, isso acontece como resultado da interação de um usuário com o app ou de alguns dados externos provenientes de uma request assíncrona ou de algum modelo de subscription.
Apps não interativos que não possuem atualizações de dados assíncronas nunca serão re-render e, portanto, não precisam se preocupar com a otimização do desempenho das novas renderizações.
Assista "Intro to re-renders" no YouTube
🧐 O que é um re-render necessário e o que é um re-render desnecessário?
Re-render necessário: re-render de um component que é a origem das alterações ou de um component que usa diretamente as novas informações. Por exemplo, se um usuário digitar um input field, o component que gerencia seu estado precisa se atualizar a cada pressionamento de tecla, ou seja, re-renderizar.
Re-render desnecessário: re-render de um component que é propagado pelo app por meio de diferentes mecanismos de re-render devido a erro ou arquitetura ineficiente do app. Por exemplo, se um usuário digitar um input field e a página inteira for re-render a cada pressionamento de tecla, a página terá sido re-render desnecessariamente.
Re-renders desnecessários por si só não são um problema: React é muito rápido e geralmente capaz de lidar com elas sem que os usuários percebam nada.
No entanto, se os re-renders acontecerem com muita frequência e/ou em components muito pesados, isso poderá fazer com que a experiência do usuário pareça com "lag", delays visíveis em cada interação ou até mesmo o app pare completamente de responder.
Assista "Intro to re-renders" no YouTube
Quando um React component re-render novamente?
Existem quatro razões pelas quais um component re-render: mudanças de state, re-renders de parents (ou childrens), mudanças de context e mudanças de hooks. Há também um grande mito: que as re-renders acontecem quando as props do component mudam. Por si só, não é verdade (veja a explicação abaixo).
🧐 Razões para re-renders: mudanças de state
Quando um state do component muda, ele irá re-render novamente. Geralmente, isso acontece ou em um callback ou no useEffect
hook.
As mudanças de state são a fonte "raiz" de todos os re-renders.
🧐 Razões para re-renders: re-render de parents
Um component será re-render se seu parent também re-render. Ou, se olharmos para isso na direção oposta: quando um component é re-render, ele também re-render todos os seus children.
Ele sempre "desce" na árvore: o re-render de um child não aciona o re-render de um parent. (Existem algumas advertências e casos extremos aqui, consulte o guia completo para mais detalhes: O mistério do React Element, children, parents e re-renders).
🧐 Razões para re-renders: mudanças de context
Quando o valor no Context Provider for alterado, todos os components que usam esse Context serão re-render, mesmo que não usem a parte alterada dos dados diretamente. Esses re-renders não podem ser evitados diretamente com a memoization, mas existem algumas workarounds que podem simular isso.
🧐 Razões para re-renders: mudanças nos hooks
Tudo o que está acontecendo dentro de um hook "pertence" ao component que o utiliza. As mesmas regras relativas às mudanças de context e state se aplicam aqui:
- a mudança de state dentro do hook irá desencadear uma re-rerender inevitável do component "host"
- se o hook usar o Context e as alterações de valor do Context, ele acionará uma re-rerender inevitável do component "host"
Hooks podem ser encadeados. Cada hook único dentro da cadeia ainda "pertence" ao "host" component, e as mesmas regras se aplicam a qualquer um deles.
⛔️ Razões para re-renders: mudanças de props (o grande mito)
Não importa se as props do component mudam, ou não, quando se fala em re-renders de components não-memoized.
Para que as props sejam alteradas, elas precisam ser atualizadas pelo parent component. Isso significa que o parent teria que re-render, o que acionaria o re-render do child component, independentemente de suas props.
Somente quando técnicas de memoization são usadas (React.memo
, useMemo
), a mudança de props se torna importante.
Evitando re-renders com composition
⛔️ Antipattern: Criando components na render function
Criar components dentro da render function de outro component é um anti-pattern que pode ser o maior destruidor de desempenho. Em cada re-render, o React irá re-mount este component (ou seja, destruí-lo e recriá-lo do zero), o que será muito mais lento do que um re-render normal. Além disso, isso levará a bugs como:
- possíveis "flashes" de conteúdo durante os re-renders
- state sendo redefinido no componente a cada re-render
- useEffect sem dependências disparadas em cada re-render
- se um component estiver focused, o focus será perdido
Mais recursos:
- Veja um exemplo no codesandbox
- Leia porque isso acontece em mais detalhes: React reconciliation: como isso funciona e por que devemos nos importar
- Assista no YouTube: Mastering React Reconciliation
✅ Evitando re-renders com composition: movendo o state "para baixo"
Esse pattern pode ser benéfico quando um component pesado gerencia o state, e esse state é usado apenas em uma pequena parte isolada da render tree. Um exemplo típico seria abrir/fechar um dialog com um clique de button em um component complicado que renderiza uma parte significativa de uma página.
Nesse caso, o state que controla a aparência do modal dialog, o próprio dialog e o button que aciona a atualização podem ser encapsulados em um component menor. Como resultado, o component maior não será re-render nessas mudanças de state.
- Veja um exemplo no codesandbox
- Leia sobre isso em mais detalhes: O mistério do React Element, children, parents e re-renders
- Leia sobre reconciliation: React reconciliation: como isso funciona e por que devemos nos importar
- Ou assista um vídeo do YouTube: Introdução aos re-renders - Advanced React Course, Episode
✅ Evitando re-renders com composition: children como props
Isso também pode ser chamado de "wrap state around children". Esse pattern é semelhante a "moving state down": ele encapsula mudanças de state em um component menor. A diferença aqui é que o state é usado em um elemento que encapsula uma parte lenta da render tree, portanto não pode ser extraído tão facilmente. Um exemplo típico seria callbacks de onScroll
ou onMouseMove
attached ao root element de um component.
Nessa situação, o state management e os components que usam esse state podem ser extraídos para um component menor, e o component lento pode ser passado para ele como children
. Da perspectiva dos components menores, children
são apenas props, portanto, não serão afetados pela mudança de state e, portanto, não serão re-render.
- Veja um exemplo no codesandbox
- Leia mais sobre composition: O mistério de React Element, children, parents e re-renders
- Leia sobre reconciliation: React reconciliation: como isso funciona e por devemos nos importar
- Ou assista sobre o pattern no YouTube: Elements, Children e Re-renders - Advanced React course, Episode 2
✅ Evitando re-renders com composition: components como props
Praticamente igual ao pattern anterior, com o mesmo comportamento: ele encapsula o state dentro de um component menor e components pesados são passados para ele como props. As props não são afetadas pela mudança de state, portanto, components pesados não serão re-render.
Pode ser útil quando alguns components pesados são independentes do state, mas não podem ser extraídos como children como um grupo.
- Veja um exemplo no codesandbox
- Leia sobre passar components como props: React component como prop: a maneira correta
- Leia sobre reconciliation: React reconciliation: como isso funciona e por que devemos nos importar
- Ou assista sobre o pattern no YouTube: Components como props - Advanced React course, Episode 3
Evitando re-renders com React.memo
Encapsulando um component em React.memo
interromperá a cadeia downstream de re-renders que é acionada em algum lugar acima da render tree, a menos que as props deste component tenham mudado.
Isso pode ser útil ao renderizar um component pesado que não depende da origem do re-render (ou seja, state, dados alterados).
- Veja um exemplo no codesandbox
- Assista um video no tópico: Mastering memoization in React - Advanced React course, Episode 5
✅ React.memo: component com props
Todas as props que não são valores primitivos devem ser memoized para que React.memo funcione
- Veja um exemplo no codesandbox
- Assista um vídeo no tópico: Mastering memoization in React - Advanced React course, Episode 5
✅ React.memo: components como props ou children
React.memo
deve ser aplicado aos elements passados como children/props. Memoize o parent component não funcionará: children e props serão objetos, portanto, eles mudarão a cada re-render.
- Veja um exemplo no codesandbox
- Leia mais sobre children e parents: O mistério de React Element, children, parents e re-renders
- Assista um vídeo no tópico: Mastering memoization in React - Advanced React course, Episode 5
Melhorando o desempenho de re-renders com useMemo/useCallback
⛔️ Antipattern: useMemo/useCallback desnecessários em props
Memoizing props por sí só não impedirá re-renders de um child component. Se um parent component re-renders, ele acionará o re-render de um child component independentemente de suas props.
- Veja um exemplo no codesandbox
- Leia mais detalhes em: Como utilizar useMemo e useCallback: você pode remover a maioria deles
- Assista um video no tópico: Mastering memoization in React - Advanced React course, Episode 5
✅ useMemo/useCallback necessário
Se um child component é encapsulado em React.memo
, todas as props que não são valores primitivos deverão ser memoized
- Veja um exemplo no codesandbox
- Assista um vídeo no tópico: Mastering memoization in React - Advanced React course, Episode 5
Se um component usa valor não-primitivo como dependência em hooks como useEffect
, useMemo
, useCallback
, ele deve ser memoized.
Veja um exemplo no codesandbox
✅ useMemo para cálculos pesados
Um dos casos de uso useMemo
é evitar cálculos caros em cada re-render.
useMemo
tem seu custo (consome um pouco de memória e torna o render inicial um pouco mais lento), portanto não deve ser usado em todos os cálculos. No React, mounting e updating components será o cálculo mais caro na maioria dos casos (a menos que você esteja realmente calculando números primos, o que você não deveria fazer no frontend de qualquer maneira).
Como resultado, o caso de uso típico useMemo
seria memoize React elements. Geralmente partes de uma render tree existente ou resultados de uma render tree gerada, como uma map function que retorna novos elements.
O custo de "pure" javascript operations, como ordenar ou filtering um array, geralmente é insignificante, em comparação com atualizações de components.
- Veja um exemplo no codesandbox
- Assista um vídeo no tópico: Mastering memoization in React - Advanced React course, Episode 5
Melhorando o desempenho de re-render de listas
Além das regras regulares e patterns de re-render, o key
attribute pode afetar o desempenho das listas no React.
Importante: apenas fornecer key
attributes não melhorará o desempenho das listas.
Para evitar rerenders de list elements, você precisa encapsular eles com React.memo
e seguir todas as práticas recomendadas.
O valor em key
deve ser uma string consistente entre os re-renders para cada elemento da lista. Normalmente, o id
do item ou o index
de arrays são usados para isso.
Não há problema em usar index
de arrays como key, se a lista for estática , ou seja, os elementos não são adicionados/removidos/inseridos/reordenados.
Usar o index do array em listas dinâmicas pode levar a:
- bugs se os itens tiverem estado ou quaisquer uncontrolled elements (como form inputs)
- desempenho degradado se os itens forem agrupados em React.memo
Recursos adicionais:
- Leia sobre keys em mais detalhes: React key attribute: melhores práticas para listas performáticas.
- Leia sobre reconciliation: React reconciliation: como isso funciona e por que devemos nos importar
- Leia sobre reconciliation: Mastering React Reconciliation - Advanced React course, Episode 6
- Veja um exemplo em codesandbox - lista estática
- Veja um exemplo em codesandbox - lista dinâmica
⛔️ Antipattern: valor randômico como key em listas
Valores gerados randomicamente nunca deveria ser usados como valores no key
attribute em listas. Elas levarão ao re-mounting de itens do React em cada nova renderização, o que levará a:
- desempenho muito ruim da lista
- bugs se os itens tiverem state ou quaisquer uncontrolled elements (como form inputs)
Veja um exemplo no codesandbox
Evitando re-renders causados pelo Context
✅ Evitando Context re-renders: memoizing o valor do Provider
Se o Context Provider não for colocado na raiz do app e houver a possibilidade de ele re-render devido a alterações em seus ancestrais, seu valor deverá ser memoized.
Veja um exemplo no codesandbox
✅ Evitando Context re-renders: splitting de dados e API
Se no contexto houver uma combinação de dados e API (getters e setters), eles poderão ser "split" (divididos) em diferentes providers no mesmo component. Dessa forma, os components que usam apenas API não serão re-render quando os dados forem alterados.
Leia mais sobre esse pattern aqui: Como escrever React apps de alto desempenho com Context
Veja um exemplo no codesandbox
✅ Evitando Context re-renders: splitting dados em chunks
Se o Context gerenciar alguns chunks de dados independentes, eles poderão ser divididos em providers menores no mesmo provider. Dessa forma, apenas os consumers do chunk alterado serão re-render.
Leia mais sobre esse pattern aqui: Como escrever React apps de alto desempenho com Context
Veja um exemplo no codesandbox
✅ Evitando Context re-renders: Context selectors
Não há como evitar que um component, que usa uma parte do valor do Context, seja re-render, mesmo que o dado usado não tenha sido alterado, mesmo com o useMemo
hook.
Context selectors, no entanto, podem ser "faked" com o uso de higher-order components e React.memo
.
Leia mais sobre esse pattern aqui: Higher-Order Components na era dos React Hooks
Veja um exemplo no codesandbox
Fonte
React re-renders guide: everything, all at once Makarevich - por Nadia Makarevich
Top comments (0)