DEV Community

Cover image for Reactjs, o 80/20 do seu melhor e mais performático código
Clayton Santos
Clayton Santos

Posted on

Reactjs, o 80/20 do seu melhor e mais performático código

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-renderizam

Cada 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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

É 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>
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
 Good: Faça
const {id} = user

useEffect(()=> {
    fetch(id)
}, [id])// agora seu useeffect roda somente se id mudar
Enter fullscreen mode Exit fullscreen mode

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];
};

Enter fullscreen mode Exit fullscreen mode
 // 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>;
}
Enter fullscreen mode Exit fullscreen mode

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"}/>
    )
}
Enter fullscreen mode Exit fullscreen mode

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}/>
Enter fullscreen mode Exit fullscreen mode
// ❌ 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}/>
Enter fullscreen mode Exit fullscreen mode

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}/>
    )
}
Enter fullscreen mode Exit fullscreen mode

Referências:
https://overreacted.io/a-complete-guide-to-useeffect/

Top comments (0)