Mantendo Componentes Puros
Algumas funções JavaScript são puros. Funções puras apenas realizam um cálculo e nada mais. Ao escrever seus componentes estritamente como funções puras, você pode evitar uma classe inteira de bugs perplexos e comportamentos imprevisíveis à medida que sua base de código cresce. Para obter esses benefícios, no entanto, há algumas regras que você deve seguir.
Você aprenderá
- O que é pureza e como ela ajuda você a evitar bugs
- Como manter componentes puros mantendo as alterações fora da fase de renderização
- Como usar o Modo Estrito para encontrar erros em seus componentes
Pureza: Componentes como fórmulas
Na ciência da computação (e especialmente no mundo da programação funcional), uma função pura é uma função com as seguintes características:
- Ela cuida de seus próprios assuntos. Não muda nenhum objeto ou variável que existia antes de ser chamada.
- Mesmos inputs, mesmo output. Dado os mesmos inputs, uma função pura deve sempre retornar o mesmo resultado.
Você pode já estar familiarizado com um exemplo de funções puras: fórmulas em matemática.
Considere esta fórmula matemática: y = 2x.
Se x = 2, então y = 4. Sempre.
Se x = 3, então y = 6. Sempre.
Se x = 3, y não será às vezes 9 ou –1 ou 2.5 dependendo da hora do dia ou do estado do mercado de ações.
Se y = 2x e x = 3, y sempre será 6.
Se transformássemos isso em uma função JavaScript, ficaria assim:
function double(number) {
return 2 * number;
}
No exemplo acima, double
é uma função pura. Se você passar 3
, ela retornará 6
. Sempre.
O React é projetado em torno desse conceito. O React assume que cada componente que você escreve é uma função pura. Isso significa que os componentes React que você escreve devem sempre retornar o mesmo JSX dado os mesmos inputs:
function Recipe({ drinkers }) { return ( <ol> <li>Ferva {drinkers} xícaras de água.</li> <li>Adicione {drinkers} colheres de chá e {0.5 * drinkers} colheres de especiarias.</li> <li>Adicione {0.5 * drinkers} xícaras de leite para ferver e açúcar a gosto.</li> </ol> ); } export default function App() { return ( <section> <h1>Receita de Chai Temperado</h1> <h2>Para dois</h2> <Recipe drinkers={2} /> <h2>Para um encontro</h2> <Recipe drinkers={4} /> </section> ); }
Quando você passa drinkers={2}
para Recipe
, ele retornará JSX contendo 2 xícaras de água
. Sempre.
Se você passar drinkers={4}
, ele retornará JSX contendo 4 xícaras de água
. Sempre.
Assim como uma fórmula matemática.
Você pode pensar em seus componentes como receitas: se você segui-las e não introduzir novos ingredientes durante o processo de “cozimento”, você obterá o mesmo prato toda vez. Esse “prato” é o JSX que o componente serve ao React para renderizar.
![Uma receita de chá para x pessoas: leve x xícaras de água, adicione x colheres de chá e 0.5x colheres de especiarias, e 0.5x xícaras de leite](/images/docs/illustrations/i_puritea-recipe.png)
Illustrated by Rachel Lee Nabors
Efeitos Colaterais: consequências (não) intencionais
O processo de renderização do React deve sempre ser puro. Os componentes devem apenas retornar seu JSX, e não mudar qualquer objeto ou variável que existia antes da renderização—isso os tornaria impuros!
Aqui está um componente que quebra essa regra:
let guest = 0; function Cup() { // Ruim: mudando uma variável preexistente! guest = guest + 1; return <h2>Xícara de chá para o convidado #{guest}</h2>; } export default function TeaSet() { return ( <> <Cup /> <Cup /> <Cup /> </> ); }
Este componente está lendo e escrevendo uma variável guest
declarada fora dele. Isso significa que chamar este componente várias vezes produzirá JSX diferente! E o que é pior, se outros componentes lerem guest
, eles também produzirão JSX diferente, dependendo de quando foram renderizados! Isso não é preditivo.
Voltando à nossa fórmula y = 2x, agora, mesmo se x = 2, não podemos confiar que y = 4. Nossos testes poderiam falhar, nossos usuários ficariam perplexos, aviões poderiam cair do céu—você pode ver como isso levaria a bugs confusos!
Você pode corrigir este componente passando guest
como uma prop em vez disso:
function Cup({ guest }) { return <h2>Xícara de chá para o convidado #{guest}</h2>; } export default function TeaSet() { return ( <> <Cup guest={1} /> <Cup guest={2} /> <Cup guest={3} /> </> ); }
Agora seu componente é puro, já que o JSX que ele retorna depende apenas da prop guest
.
Em geral, você não deve esperar que seus componentes sejam renderizados em uma ordem particular. Não importa se você chama y = 2x antes ou depois de y = 5x: ambas as fórmulas serão resolvidas independentemente uma da outra. Da mesma forma, cada componente deve “pensar por si mesmo”, e não tentar coordenar ou depender de outros durante a renderização. Renderização é como um exame escolar: cada componente deve calcular JSX por conta própria!
Deep Dive
Embora você ainda possa não ter usado todos, no React existem três tipos de inputs que você pode ler enquanto renderiza: props, state, e context. Você deve sempre tratar esses inputs como somente leitura.
Quando você deseja mudar algo em resposta a uma entrada do usuário, você deve definir estado em vez de escrever para uma variável. Você nunca deve mudar variáveis ou objetos preexistentes enquanto seu componente está sendo renderizado.
O React oferece um “Modo Estrito” no qual ele chama a função de cada componente duas vezes durante o desenvolvimento. Ao chamar as funções dos componentes duas vezes, o Modo Estrito ajuda a encontrar componentes que quebram essas regras.
Note como o exemplo original exibiu “Convidado #2”, “Convidado #4”, e “Convidado #6” em vez de “Convidado #1”, “Convidado #2”, e “Convidado #3”. A função original era impura, então chamá-la duas vezes a quebrou. Mas a versão pura corrigida funciona mesmo se a função for chamada duas vezes todas as vezes. Funções puras apenas calculam, então chamá-las duas vezes não muda nada—assim como chamar double(2)
duas vezes não muda o que é retornado, e resolver y = 2x duas vezes não muda o que y é. Mesmos inputs, mesmos outputs. Sempre.
O Modo Estrito não tem efeito na produção, então não desacelerará o aplicativo para seus usuários. Para optar pelo Modo Estrito, você pode envolver seu componente raiz em <React.StrictMode>
. Alguns frameworks fazem isso por padrão.
Mutação Local: O pequeno segredo do seu componente
No exemplo acima, o problema foi que o componente mudou uma variável preexistente durante a renderização. Isso é frequentemente chamado de “mutação” para torná-lo um pouco mais assustador. Funções puras não mutam variáveis fora do escopo da função ou objetos que foram criados antes da chamada—isso as torna impuras!
No entanto, é completamente normal mudar variáveis e objetos que você acabou de criar durante a renderização. Neste exemplo, você cria um array []
, atribui a uma variável cups
, e depois push
uma dúzia de xícaras nele:
function Cup({ guest }) { return <h2>Xícara de chá para o convidado #{guest}</h2>; } export default function TeaGathering() { let cups = []; for (let i = 1; i <= 12; i++) { cups.push(<Cup key={i} guest={i} />); } return cups; }
Se a variável cups
ou o array []
fossem criados fora da função TeaGathering
, isso seria um enorme problema! Você estaria mudando um objeto preexistente ao inserir itens naquele array.
No entanto, está tudo bem porque você os criou durante a mesma renderização, dentro de TeaGathering
. Nenhum código fora de TeaGathering
saberá que isso aconteceu. Isso é chamado de “mutação local”—é como o pequeno segredo do seu componente.
Onde você pode causar efeitos colaterais
Embora a programação funcional dependa fortemente da pureza, em algum momento, em algum lugar, algo precisa mudar. Esse é o ponto da programação! Essas mudanças—atualizar a tela, iniciar uma animação, mudar os dados—são chamadas de efeitos colaterais. Elas são coisas que acontecem “à parte”, não durante a renderização.
No React, efeitos colaterais geralmente pertencem a manipuladores de eventos. Manipuladores de eventos são funções que o React executa quando você realiza alguma ação—por exemplo, quando você clica em um botão. Mesmo que os manipuladores de eventos sejam definidos dentro do seu componente, eles não são executados durante a renderização! Então, manipuladores de eventos não precisam ser puros.
Se você esgotou todas as outras opções e não consegue encontrar o manipulador de eventos certo para seu efeito colateral, ainda pode anexá-lo ao seu JSX retornado com uma chamada useEffect
em seu componente. Isso diz ao React para executá-lo mais tarde, após a renderização, quando os efeitos colaterais são permitidos. No entanto, essa abordagem deve ser seu último recurso.
Quando possível, procure expressar sua lógica apenas com a renderização. Você ficará surpreso com o quão longe isso pode levá-lo!
Deep Dive
Escrever funções puras requer algum hábito e disciplina. Mas também desbloqueia oportunidades maravilhosas:
- Seus componentes podem ser executados em um ambiente diferente—por exemplo, no servidor! Como eles retornam o mesmo resultado para os mesmos inputs, um componente pode servir a muitos pedidos de usuários.
- Você pode melhorar o desempenho ao pular a renderização de componentes cujos inputs não mudaram. Isso é seguro porque funções puras sempre retornam os mesmos resultados, assim, elas são seguras para armazenar em cache.
- Se alguns dados mudarem no meio da renderização de uma árvore de componentes profunda, o React pode reiniciar a renderização sem perder tempo para terminar a renderização desatualizada. A pureza torna seguro parar de calcular a qualquer momento.
Cada novo recurso do React que estamos construindo tira proveito da pureza. Desde a busca de dados até animações e desempenho, manter os componentes puros desbloqueia o poder do paradigma React.
Recap
- Um componente deve ser puro, significando:
- Ela cuida de seus próprios assuntos. Não deve mudar nenhum objeto ou variável que existia antes da renderização.
- Mesmos inputs, mesmo output. Dado os mesmos inputs, um componente deve sempre retornar o mesmo JSX.
- A renderização pode acontecer a qualquer momento, então os componentes não devem depender da sequência de renderização uns dos outros.
- Você não deve mutar nenhum dos inputs que seus componentes usam para renderização. Isso inclui props, state e context. Para atualizar a tela, “defina” estado em vez de mutar objetos preexistentes.
- Procure expressar a lógica do seu componente no JSX que você retorna. Quando você precisar “mudar as coisas”, geralmente desejará fazê-lo em um manipulador de eventos. Como último recurso, você pode usar
useEffect
. - Escrever funções puras requer um pouco de prática, mas desbloqueia o poder do paradigma do React.
Challenge 1 of 3: Corrija um relógio quebrado
Este componente tenta definir a classe CSS do <h1>
como "night"
durante o tempo da meia-noite até seis horas da manhã, e "day"
em todos os outros momentos. No entanto, não funciona. Você pode corrigir este componente?
Você pode verificar se sua solução funciona temporariamente mudando o fuso horário do computador. Quando a hora atual estiver entre meia-noite e seis da manhã, o relógio deve ter cores invertidas!
export default function Clock({ time }) { let hours = time.getHours(); if (hours >= 0 && hours <= 6) { document.getElementById('time').className = 'night'; } else { document.getElementById('time').className = 'day'; } return ( <h1 id="time"> {time.toLocaleTimeString()} </h1> ); }