Entender como flui a renderização é um ponto que pra mim, mudou a maneira como eu desenvolvo meus componentes. Vamos explorar em alto nível, alguns pontos sobre a renderização e como as props fluem. Saber isso poupará você de bugs inesperados e difíceis de depurar e te ajudará a escrever código mais enxutos. É básico, pontual e direto. Espero que te ajude. Vamos lá.
As props seguem o fluxo de cima para baixo.
Se um o componente Pai re-renderiza, então os filhos re-renderizamCada renderização tem seu próprio conjunto de props, states, funcões etc. Isso faz com que cada renderização seja única.
Snapshots: cada renderização tem seu próprio conjunto de states e props.
Observe o código abaixo:
function Calculator() {
const [count, setCount] = useState(0);
return (
<>
<b>{count}</b>
<button onClick={handleClick}>Increment</button>
</>
);
}
É fácil pensar que pode existir um tipo de ligação entre a varíavel count e seu state, mas isso não é o que acontece de fato. Sempre que count muda, a função do seu componente é chamada novamente criando uma “foto” no tempo de todo o state e props naquele momento. Então:
// Durante primeira renderização
function Counter() {
const count = 0; // Returned by useState()
// ...
<p>You clicked {count} times</p>
// ...
}
// Após um clique, nossa função é chamada novamente
function Counter() {
const count = 1; // Returned by useState()
// ...
<p>You clicked {count} times</p>
// ...
}
// Após outro clique, nossa função é chamada novamente
function Counter() {
const count = 2; // Returned by useState()
// ...
<p>You clicked {count} times</p>
// ...
}
O que não é obvio sobre re-renderizações
Não importa se o seu componente recebe uma prop ou não. Ele sempre vai re-renderizar se o seu componente Pai re-renderizar. Esse é o default.
function Parent(){ // Quando Parent re-renderizar, Child re-renderizará
return <Child>
}
Aqui está um componente Log que sempre será renderizado se o Parent re-renderizar. Como dito, esse é o comportamento default do React e nesse caso é intencional que Log seja sempre re-renderizado.
import React, { useState, memo } from "react";
function Log() {
return <section>{String(new Date())}</section>;
}
function App() {
const [, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount((count) => count + 1)}>Increment</button>
<Log /> // 🔄 re-renderiza sempre que App re-renderizar
</div>
);
}
Como re-renderizar seu componente apenas se as props mudarem?
Você precisa dizer para seu componente se importar com as props e compará-las com as renderizações anteriores. A forma de fazer isso é memoizando seu componente. Memoizar seu componente, siginifica que você precisa envolve-lo com React.memo. Observe o código abaixo:
function Log() {
return <section>{new Date()}</section>;
}
const LogWithMemo = React.memo(Log);
function Parent() {
return <LogWithMemo />; // não re-renderiza se Parent Re-renderizar
}
Note que no exemplo acima, para o nosso caso de uso, era intencional que Log sempre re-renderizasse, mostrando a hora que foi atualizado. No entanto, para o exemplo com React.memo, nós teriamos um bug no qual a data nunca é atualizada. É um bom exemplo de como memoizar tudo nem sempre é o ideal.
UseCalback Pitfalls: Quando a memoização não funciona.
Memoizar uma função com o intuito de evitar re-renders nos componentes que a recebe é inútil se o componente não for um componente memoizado.
Contexto: envolvi a função doSomething em um useCalback. Como ele não depende de nenhuma dependência, a referência de handleClick é a mesma entre cada render.
Nesse contexto, a regra é: useCalback só será últil se o componente estiver memoizado( usar React.memo )
function Parent(){
const handleClick = useCalback(doSomething, [])
return <Child onClick={handleClick}/> //❗ atenção: sempre re-renderiza se Parent re-renderizar
}
function Parent(){
const handleClick = useCalback(()=> {}, [])
return <ChildWithMemo onClick={handleClick}/> // ✅ funciona. Só re-renderiza se as props mudarem
}
UseEffect Pitfalls
Você acha que precisa de um useEffect para calcular um valor quando uma prop ou state for alterado.
Problema: States, useEffects e renders desnecessários.
❌ Bad
function Calculator({ count }){
const [value, setValue] = useState(null)
useEffect(() => {
setValue(calculate(count))
}, [count])
return <div>{value}</div>
}
Faça: O fluxo do react segue de cima para baixo. Quando uma prop ou state muda, seu componente re-renderiza. Dito isso, apenas calcule o valor enquanto seu componente renderiza.
✅ Good
function Calculator({ count }){
const value = calculate(count)
return <div>{value}</div>
}
Seu useEffect é executado mais que o necessário.
Contexto: Você acha que precisa inserir todo um objeto nas dependências de um useEffect.
Não importa se você lê antes ou depois as pros. Numa renderização elas sempre serão as mesmas. então:
❌ Bad: Evite
useEffect(()=> {
fetch(user.id)
}, [user]) // roda sempre que o objeto user muda
✅ Good: Faça
const {id} = user
useEffect(()=> {
fetch(id)
}, [id])// agora seu useeffect roda somente se id mudar
UseEffect: você acha que tá tudo bem omitir dependências
Contexto: eu tenho uma app simples na qual eu alterno entre ligas clicando em botões;
Problema: o nome da liga nunca é alterado depois do primeiro render.
Motivo: eu omiti a função que tem as informações "atualizadas"!
function App() {
const [idLeague, setIdLeague] = React.useState(1);
return (
<>
<League id={idLeague} />
<button onClick={() => setIdLeague(1)}>Liga Inglesa</button>
<button onClick={() => setIdLeague(2)}>Liga Espanhola</button>
</>
);
}
const getTimesById = (id) => {
return {
1: {
name: "Inglesa"
},
2: {
name: "Espanhola"
}
}[id];
};
❌ // id muda, mas o useEffect não atualiza os dados
// pois nós omitimos a função que usar a prop id, nas dependencias do useEffect.
function League({ id }) {
const [league, setLeague] = useState([]);
const handleGetTimes = useCallback(() => setLeague(getTimesById(id)), [id]);
useEffect(() => {
handleGetTimes();
}, [isFirstPage]); ❌ // omitir a handleGetTimes aqui causa o bug!
return <div>{league?.name}</div>;
}
Wrappers denecessários
Isso é comum e não muito útil. Geralmente isso é feito com o pensamento de que se isso se repete, então é melhor eu criar um componente que encapsula. Mas isso é totalmente desnecessário. Veja o exemplo abaixo:
import { Text } from '@components'
function TextCustom ({color}){
return <Text color={color}/>
}
function Blog(){
return (
<TextCustom color={"red"}/>
//...outros jsx aqui
<TextCustom color={"blue"}/>
<TextCustom color={"red"}/>
)
}
Eu diria, por que não usar o Text direto?
E se você precisar de uma nova prop?
E se você precisar de várias props?
// ❌ Bad: uma nova prop
function TextCustom ({color, fontSize}){
return <Text color={color, fontSize}/>
}
<TextCustom color={"red"} fontSize={20}/>
// ❌ Bad: você se irritou de adicionar props então você apenas repassa todas as props
// sem nenhuma validação
function TextCustom (props){
return <Text { ...props }/>
}
<TextCustom color={"red"} fontSize={20}/>
Então, explicado isso, apenas use o componente diretamente:
✅ Good
function Blog(){
return (
<Text color={"red"} />
//...outros jsx aqui
<Text color={"blue"}/>
<Text color={"red"} fontSize={20}/>
)
}
Referências:
https://overreacted.io/a-complete-guide-to-useeffect/
Top comments (0)