O lançamento da nova documentação do React em março de 2023 foi, para mim, uma ótima oportunidade para revisar alguns conceitos básicos para os quais eu nunca havia dado tanta atenção, e, por incrível que pareça, uma das leituras que mais me chamou a atenção foi a dessa seção, que explica sobre dois conceitos presentes desde a concepção do React: UI reativa e programação declarativa.
De forma resumida, o React permite criar interfaces de usuário que reagem a mudanças no estado de uma aplicação e disparar as atualizações correspondentes na UI por meio de descrições sobre como ela deve se parecer em vez de comandos para alterar a interface diretamente. Em outras palavras, o desenvolvedor não se preocupa com como exibir a interface, mas com o que deve ser exibido.
Em React, você não manipula diretamente a UI — isso é, você não habilita, desabilita, mostra ou oculta componentes diretamente. Em vez disso, você declara o que quer mostrar e o React identifica como atualizar a UI. (tradução nossa.)
Trata-se de um tema bem interessante, mas antes de entrar em mais detalhes, eu gostaria de te mostrar o que todo esse assunto tem a ver com cortar o seu próprio cabelo. Ficou com curiosidade? Então continue comigo até a próxima seção!
Cortes de cabelo podem ser complicados; a UI também
Flávio é um rapaz jovem e se preocupa com o seu visual, mas não esteve satisfeito com o seu cabelo nos últimos dias e sente que está na hora de experimentar um novo corte, só que com um detalhe: ele mesmo queria fazê-lo.
A primeira etapa foi comprar as ferramentas necessárias, como uma tesoura, uma máquina de cortar cabelo e um pente. Em seguida, estudou algumas técnicas de barbearia pela internet e, por fim, iniciou o trabalho em sua própria casa.
Após terminar, Flávio limpou o local, tomou um banho, penteou o cabelo e, quando se olhou no espelho, teve uma surpresa infeliz ao perceber que o resultado não o agradou tanto quanto esperava — seria melhor ter ido ao cabeleireiro — ele pensou.
Figura 1 — Flávio após ver o seu cabelo no espelho (GIF via tenor.com)
Ao meu ver, essa situação pode ser comparada a de um desenvolvedor front-end implementando uma aplicação web moderna, cuja interface de usuário precisa ser constantemente atualizada para acompanhar as interações com o usuário.
Do mesmo modo que Flávio poderia ter apenas explicado como queria seu cabelo para que um cabeleireiro fizesse o corte, tendo em mãos as ferramentas, técnicas e espaço adequados; um desenvolvedor web poderia ver o seu trabalho facilitado ao dispor de uma biblioteca de código que recebesse descrições da interface de usuário e fizesse os trabalhos de exibi-la e atualizá-la da forma adequada.
Figura 2 — Flávio, caso tivesse ido ao cabeleireiro (GIF via tenor.com)
Com isso, não estamos reconhecendo ser impossível realizar todo esse trabalho por conta própria, afinal, era assim que tudo era feito até alguns anos atrás. Acontece que esse processo pode ser complexo, sujeito a erros e nem sempre será feito de forma eficiente quando falamos em grandes aplicações, e é nesse sentido que bibliotecas de UI como o React entram em cena.
Mas como descrever uma interface de usuário?
Diferentemente de como Flávio poderia simplesmente descrever o seu corte para o cabeleireiro, nós não temos uma forma tão trivial para solicitar ao computador que exiba as interfaces de usuário, pois não falamos a mesma língua. Logo, justifica-se a necessidade de encontrar uma maneira padronizada para descrever a UI, assim como o React utiliza a sintaxe do JSX para esse fim.
Ocorre que, além de descrever e exibir os “esqueletos” de componentes da interface com JSX — isso é, apenas as suas estruturas — precisamos gerenciar a exibição de conteúdo dinâmico, que varia conforme as “situações” em que cada componente se encontra. Esse comportamento é abordado e implementado em React por meio do conceito de estado e do hook useState
.
Utilizando esses dois recursos — JSX e estado — implementamos componentes em React por meio de funções que, a grosso modo, têm como principal objetivo expor uma forma de obter descrições atualizadas de um componente sempre que houver alterações no seu estado, sendo que a esse evento, em que o React chama uma dessas funções, atribui-se o nome de “renderização”.
Para ser um pouco mais específico, o fluxo de atualização de um componente se assemelha ao descrito a seguir:
-
o estado do componente, que é gerenciado de forma centralizada e interna ao React, sofre uma alteração (por exemplo, devido à chamada de uma função
set
); -
o React invoca a função de renderização do componente em questão e fornece a ela uma cópia com os novos valores das suas variáveis de estado. O valor retornado é uma representação válida para o componente naquele momento;
-
com a nova representação do componente em mãos, o React transfere as mudanças necessárias ao DOM, o que provoca uma atualização da interface exibida pelo navegador para corresponder ao estado atual do componente.
A base de conhecimento do React fornece uma explicação ainda mais detalhada sobre esse assunto, mas, de forma geral, podemos sintetizar esse fluxo conforme ilustrado na figura 3.
Figura 3 — componente visualizado como uma função matemática (baseado na ilustração de Asaf Varon)
Bônus: chamadas cumulativas de funções “set”
Uma consequência do processo de renderização em React diz respeito a uma limitação sobre como devemos utilizar funções set
para alterar variáveis de estado de um componente, mais especificamente, quando tentamos chamá-las “de uma só vez” para aplicar mudanças cumulativas. Para entender melhor o que eu quero dizer, veja o exemplo a seguir:
// trecho de código retirado de https://react.dev/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time
// Código omitido
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
// Código omitido
Quando estamos começando em React, é comum chegarmos à conclusão de que o resultado ao clicar no botão descrito acima (ou ao disparar qualquer evento semelhante) seja um incremento de três unidades no valor de number
, mas, surpreendentemente, o que ocorre é um incremento de uma única unidade.
O motivo por trás disso é que, uma vez que o JSX é processado pela função de render do componente, a representação resultante sempre “enxerga” os valores de estado (nesse caso, o de number
) como estavam no momento da renderização, e, até que o componente seja “chamado” novamente pelo React, ele não recebe o estado atualizado.
Como resultado, cada chamada de setNumber
enxerga o valor de number
somente como ele estava no momento em que onClick
foi chamado, pois todas foram feitas antes que o componente fosse renderizado novamente. Logo, cada uma solicita a atualização de number
para o mesmo valor (o valor inicial somado a 1).
Considerações finais
Podemos dizer que o tema tratado neste artigo é introdutório e até mesmo faz parte dos fundamentos do React, mas acredito que ter um entendimento claro sobre ele ajuda a construir uma base sólida, que facilita compreender o funcionamento da biblioteca com mais detalhes, por exemplo, da forma que me ajudou a entender com maior propriedade o fenômeno descrito na seção bônus.
Em suma, você deve se lembrar que o React segue um paradigma declarativo, cuja ideia é descrever a UI em vez de controlá-la diretamente, em contraste com uma abordagem imperativa, e fazemos isso por meio de funções de render, que fornecem descrições atualizadas dos componentes após mudanças em seus estados, sendo utilizadas pelo React para descobrir como ele deve atualizar o DOM.
A figura 4 sintetiza a diferença entre os paradigmas declarativo e imperativo.
Figura 4 — Paradigma imperativo vs. paradigma declarativo
Finalmente, deixo explícito que vários outros detalhes sobre o funcionamento do React foram omitidos no contexto deste artigo, como o gerenciamento de estado, Virtual DOM e o processo de reconciliação em geral. Sendo assim, sugiro que continue estudando sobre esses assuntos, sendo a base de conhecimento do React a minha principal recomendação (por sinal, foi a maior fonte de informações para a escrita deste texto).
Referências
-
Pete Hunt: React: Rethinking best practices — JSConf EU (YouTube)
-
Render and Commit. Step 2: React renders your components (react.dev)
-
Trabalhando com o Compose. O paradigma de programação declarativa (Android Developers)
-
7 Principles of Rich Web Applications - Guillermo Rauch (rauchg.com)
Atribuições
- Fotografia da capa por Chris Knight em Unsplash
- Logo da marca React sob a licença Creative Commons Attribution 4.0