Neste artigo, pretendemos entender por que você deve considerar o HTMX como uma substituição para o React na próxima vez que escolher uma pilha de tecnologia para um aplicativo web. Vamos analisar a complexidade e os desafios que uma API JSON HTTP tradicional + React trazem e como você pode facilmente evitá-los ao usar o HTMX.
OBS: Neste artigo, vou me referir ao React, mas ele pode ser substituído por qualquer outro framework de front-end, como Angular, Vue, Svelte ou Solid. No entanto, falo sobre o React porque é a tecnologia padrão para a qual a maioria dos desenvolvedores web recorre.
O que exatamente é o HTMX
Caso você ainda não saiba, o HTMX é uma pequena biblioteca para navegadores (JavaScript) que estende o HTML com alguns atributos que permitem atualizar partes de uma página web com uma resposta do servidor. Ele também possibilita que o HTML faça solicitações HTTP em todos os verbos, não apenas GET e POST.
Qual problema o React resolve
O React é uma biblioteca JavaScript que ajuda você a criar aplicativos altamente interativos mantendo a interface do usuário em sincronia com o estado do aplicativo. Você informa ao React como renderizar um estado específico, e sempre que o estado é atualizado, ele irá re-renderizar (o mais eficientemente possível) a interface do usuário para refletir as mudanças no estado.
Cada vez que o estado muda, você notifica a biblioteca de que ele mudou e fornece o novo estado, e o React cuidará das atualizações da interface do usuário.
Exemplos de aplicativos altamente interativos que necessitam de um estado local em memória podem incluir diversos editores de texto que você pode encontrar na web (como o VSCode), um quadro kanban de arrastar e soltar como o Trello ou o JIRA, um reprodutor de vídeo ou uma sala de bate-papo.
O que não é um exemplo de tal aplicativo? A lista de tarefas que você está construindo, o site de notícias que você está lendo, o blog em que você está postando e a maioria dos sites por aí. Se seguirmos a regra 80/20:
80% dos efeitos vêm de 20% das causas e 80% dos resultados vêm de 20% do esforço.
Você pode argumentar que 80% dos aplicativos web que usam o React não precisam de um estado local. E, dos 20% que precisam, você pode argumentar que é apenas uma pequena parte do aplicativo (cerca de 20%), e o restante pode ser expresso apenas em HTML.
É importante observar que os números mencionados são arbitrários e não têm respaldo em pesquisas concretas. A adequação do React ou de qualquer outra tecnologia pode variar de acordo com o caso.
O que o React também resolveu e o que o tornou amplamente adotado para websites modernos
O HTML é antigo e desatualizado. As abordagens antigas para criar aplicativos com HTML envolviam uma coleção de páginas, links e formulários que descreviam ao usuário o estado atual de um recurso específico e o que eles podiam fazer para alterá-lo.
Cada vez que o usuário interagia com um recurso, a aplicação só podia recarregar a página inteira para exibir o novo estado do recurso.
Alguns anos depois, o Facebook introduziu o React, uma biblioteca JavaScript que permitiu aos desenvolvedores criar aplicativos de página única (SPA). Não era mais necessário recarregar a página inteira ao navegar, e surgiram transições suaves para atualizações de estado, feedback interessante para o usuário e outras melhorias que fizeram com que os desenvolvedores web adotassem Frameworks de SPA para seus websites.
A Questão da Complexidade
Se você não entende o esquema acima, não se preocupe, não há nada para entender. Eu pedi ao ChantGPT que o gerasse para mim, e, como ele é excessivamente complicado e não faz sentido algum, ele reflete perfeitamente a infraestrutura padrão atual para um aplicativo web moderno.
Um princípio de programação interessante é o KISS, que significa "Keep it Stupid Simple," ou, como alguns gostam de brincar, "Mantenha Simples, Estúpido!"
A infraestrutura e a pilha tecnológica atuais às quais os desenvolvedores modernos recorrem para criar aplicativos web são extremamente complicadas, fazendo muitas coisas que não precisam, apenas porque é considerado legal!
Funciona bem quando você está construindo o primeiro POC (Prova de Conceito) por conta própria, mas no momento seguinte, quando você adiciona mais membros à equipe e adota uma abordagem ágil com iterações múltiplas e "abraçando" as mudanças, ela meio que começa a apresentar problemas, por razões que examinaremos mais adiante.
O Problema de Gerenciamento de Estado com uma API JSON HTTP Tradicional + React
O que frequentemente precisa ser feito em um aplicativo web é obter o estado de um recurso no banco de dados e apresentá-lo ao usuário. Vamos pegar o exemplo de um aplicativo de gerenciamento de tarefas. O usuário possui uma lista de tarefas, e cada tarefa possui:
- Título da tarefa
- Descrição
- Um indicador se a tarefa foi concluída
- Uma data de vencimento (opcional)
Normalmente, armazenamos esse estado em um banco de dados e, para apresentar essas informações ao usuário, você precisa:
- Obter todas as tarefas do banco de dados às quais o usuário tem acesso.
- Opcionalmente, transformar os dados (talvez você armazene a data em que foi concluída e calcule o indicador "is_completed" a partir disso).
- Serializar os dados em JSON.
- Buscar os dados por meio de uma solicitação HTTP.
- (opcional, mas geralmente) validar os dados em relação a um esquema, provavelmente com YUP ou ZOD.
- Transformar o JSON em um estado e armazená-lo em um cache usando Redux, Zustand, react-query ou outra biblioteca de gerenciamento de estado.
- Transformar esse estado em HTML, geralmente descobrindo o que o usuário pode fazer com os dados.
Em resumo, estamos descrevendo como renderizar todos os possíveis estados de todos os recursos em JavaScript, baixar esse JavaScript no navegador e, em seguida, o JavaScript faz o download de uma série de dados no formato JSON e os renderiza (se souber como) no navegador como HTML!
Isso é um monte de trabalho para mostrar uma lista de tarefas ao usuário, especialmente quando as tarefas mudam apenas quando o usuário as altera, e para fazer isso, o aplicativo precisa colocar o aplicativo em um estado de carregamento, fazer outra solicitação HTTP (para PUT, PATCH ou DELETE), invalidar o valor em cache (o estado) e buscá-lo novamente para exibir a tarefa alterada.
Ou, pior ainda, quando o usuário altera alguma tarefa, atualize o estado local de forma otimista e mostre a alteração imediatamente, realizando a solicitação de atualização em segundo plano, apenas para notificar o usuário de que a atualização falhou após a atualização bem-sucedida.
Isso é extremamente propenso a erros. Pode funcionar bem para este aplicativo de lista de tarefas, onde você é o único desenvolvedor e o aplicativo é pequeno o suficiente para que você possa manter um mapa mental de tudo o que está acontecendo. No entanto, quando você tem uma equipe maior, especialmente quando divide a equipe entre front-end e back-end, muitos problemas podem surgir devido à falta de comunicação.
O back-end pode usar o indicador "is_completed", enquanto o front-end pode esperar um indicador "is_active". O back-end pode enviar a descrição processada de markdown para HTML, enquanto o front-end pode esperar que ela não tenha sido processada. O back-end pode tornar a descrição opcional para permitir que os usuários salvem rascunhos, enquanto o front-end não está em sincronia, e você verá muitos "Uncaught TypeError: Cannot read properties of undefined (reading 'toLowerCase')".
Por outro lado, com o HTMX, você renderiza o HTML diretamente no modelo, tão seguro quanto a linguagem do seu back-end permite. Você envia apenas as informações relevantes para o navegador, apresenta ao usuário os controles apropriados sobre o recurso e instrui o navegador ou o HTMX sobre como interpretar as ações do usuário e a resposta do back-end a essas ações. Todo o estado da aplicação é o HTML, um conceito conhecido como HATEOAS.
A Necessidade de Documentação para uma API JSON HTTP Tradicional + React
Para que duas equipes (front-end e back-end) trabalhem independentemente e se comuniquem via API JSON HTTP, é necessário ter uma documentação adequada da API. Você também precisa documentar como calcular quais ações um usuário pode executar em um recurso específico para exibir os controles.
A maior parte desse tipo de documentação é cansativa de escrever, especialmente porque geralmente é necessária antes de ser implementada, quando o desenvolvedor ainda não compreende completamente o escopo do problema, para que o desenvolvimento do front-end possa ocorrer em paralelo. Isso geralmente resulta em muitas atualizações durante o desenvolvimento para ajustar os problemas que surgem durante o processo e pode levar a versões desalinhadas entre as equipes.
Você também precisa versionar a API e ter cuidado para não introduzir alterações incompatíveis em alterações de versão não principais. Você não pode mais alterar o nome de um campo sem aumentar a versão principal. Você também precisa manter várias versões da API em execução ou forçar a equipe de front-end a se adaptar.
E na maioria das vezes, a documentação fica desatualizada. Algumas partes dela precisam ser corrigidas com urgência, algumas novas exigências surgem no dia anterior ao lançamento, e agora sua documentação está desatualizada, mesmo que por um curto período de tempo. E você precisa lembrar de atualizá-la, ou pior, criar um ticket para se lembrar disso, e outra pessoa pega esse ticket e não tem uma visão completa e documenta errado!
O Problema da Lógica Duplicada
Para cada recurso, você precisa implementar políticas de autorização. Você deve determinar se o usuário atual pode marcar a tarefa 46234 como concluída. Em algum lugar do código back-end, você deve escrever essa verificação. Caso contrário, você deixa seu aplicativo vulnerável a referência direta a objetos insegura, ou qualquer pessoa com o Postman pode marcar sua tarefa como concluída.
Você também precisa implementar a mesma lógica no front-end, para mostrar o botão de marcação apenas se o usuário tiver permissão para marcá-lo como concluído (vamos supor que você possa compartilhar suas tarefas com outros usuários, mas apenas você pode modificá-las).
Agora, toda vez que essa lógica muda, você deve implementá-la em ambas as aplicações e lançá-la ao mesmo tempo ou ter várias versões da API.
O Problema de Desempenho
Para renderizar um site no navegador com React, é necessário agrupar o código do React, que possui uma pegada gigantesca na memória e impacto na análise/processamento, o código da biblioteca de gerenciamento de estado, o código da biblioteca de interface do usuário, o código da biblioteca CSS-IN-JS, o código do aplicativo e qualquer biblioteca JavaScript que instalamos e usamos com o NPM. Isso resulta geralmente em ativos JavaScript volumosos a serem entregues pela rede. Claro, é possível fazer cache no navegador, mas em desenvolvimento ágil moderno, você faz implantações pelo menos uma vez por sprint, então isso não resolve nada. Isso consome tráfego de rede e bateria, um problema muitas vezes ignorado em dispositivos móveis.
O JavaScript mencionado acima precisa ser interpretado pelo navegador, consumindo assim poder de processamento e bateria.
O JavaScript, especialmente o ReactDOM, precisa acompanhar um espelho do DOM. Some o DOM normal a isso, o cache de estado local, todas as funções de renderização, todas as funções useMemo e useCallback e useState. Adicione também todos os closures que precisam manter na memória todas as variáveis de contexto. E os mecanismos JavaScript não são conhecidos por sua eficiência de memória! Você ouve as pessoas lamentando o quanto de memória o navegador consome, mas elas subestimam o quanto disso vem dos sites que visitam.
Tudo isso se acumula e acaba drenando a bateria e a memória dos usuários. Claro, é possível se esforçar e otimizar tudo isso, ou usar outra biblioteca como o Svelte, mas todo esse esforço pode ser direcionado para a entrega de recursos mais significativos para seus usuários.
A Necessidade de Renderização do Lado do Servidor
Nos últimos anos, vimos o surgimento de frameworks especializados em renderização do lado do servidor, como o Next.js. Sua popularidade destaca a necessidade de entregar conteúdo no formato HTML, especialmente por razões de otimização de acessibilidade, desempenho e otimização para mecanismos de busca.
Você não quer esperar o navegador baixar o JavaScript para renderizar a página, depois esperar o JavaScript fazer solicitações HTTP para obter o conteúdo e, em seguida, renderizá-lo. Você quer que seja renderizado imediatamente, especialmente para o conteúdo acima da dobra.
Isso adiciona mais uma camada de complexidade, incluindo:
- A infraestrutura, agora você precisa de outro servidor para o aplicativo front-end.
- O código fica mais complexo, incluindo o mapa mental do código que é executado no servidor e no navegador.
- Os pipelines de implantação agora são mais complexos.
- A infraestrutura de testes agora é mais complexa.
- Solucionar um problema agora é mais difícil, você precisa entender se o problema está no navegador, no servidor do aplicativo cliente ou no servidor da API.
Resolvendo Esses Problemas
A comunidade de desenvolvimento web, em suas respectivas linguagens e tecnologias, trabalha para resolver esses problemas de maneiras diferentes:
- Next.js (e Nuxt e similares)
- React server components
- Laravel
- Inertia.JS
- Livewire
- DotNet
- Blazor Pages
- Elixir
- Phonix LiveView
- Rust
E muitas outras soluções das quais eu talvez tenha esquecido ou nunca ouvido falar!
De qualquer forma, a existência e popularidade dessas soluções são a prova de que esses problemas são reais e encontrados no dia a dia de um desenvolvedor web. Caso contrário, eles não se esforçariam para resolvê-los, especialmente de forma de código aberto!
Há também o Turbo e os frameworks que o adotam, como Ruby on Rails, PHP Symfony e possivelmente outros que resolvem o mesmo problema da mesma forma que o HTMX. A escolha pelo HTMX é uma questão de gosto pessoal, mas você definitivamente deve aprender sobre isso, pois é tão interessante quanto o HTMX!
Dentre todas essas soluções, o HTMX se destaca, não apenas porque não o prende a uma tecnologia específica (você pode alternar de PHP para Rust com alterações mínimas nos modelos), mas também porque ele elimina completamente a necessidade de componentes com estado ou a necessidade de acompanhar um estado específico do aplicativo que não está relacionado a recursos.
Por exemplo, vamos pegar um modal de confirmação. Normalmente, você acaba criando um estado em memória para determinar se ele está aberto e o exibe ao usuário com base nesse estado. No HTMX, o estado É O PRÓPRIO HTML, o que significa que, quando você clica para abrir o modal, você FAZ UMA REQUISIÇÃO GET para "tasks/{taskId}/confirm-delete" e incorpora o HTML de resposta no DOM. E quando é excluído, você remove o modal e a tarefa completamente! Isso resolve todos os problemas mencionados acima de uma maneira única e extremamente simples. Você não precisa:
- Acompanhar o estado
- Saber como renderizar o diálogo
- Documentar a API
- Verificar se o usuário pode excluir a tarefa (no front-end)
- Seu aplicativo back-end está sempre no controle
- Você obtém melhor segurança, pois não envia dados irrelevantes para o navegador e evita informações sensíveis
- Você obtém melhor desempenho
- E, o mais importante, mantém seu aplicativo extremamente simples e permite a complexidade apenas quando ela resolve problemas dos usuários!
Você apenas instrui o HTMX de onde obter o diálogo e onde colocá-lo, e ele cuida de tudo!
Conclusão
No cenário em constante evolução do desenvolvimento web, é revigorante ver ferramentas como o HTMX que defendem a simplicidade e o retorno aos conceitos básicos. Ao aproveitar o poder do HTML e do HTTP, o HTMX permite que os desenvolvedores criem aplicativos web dinâmicos sem as complexidades e o overhead dos frameworks JavaScript tradicionais.
Portanto, da próxima vez que você estiver iniciando um novo projeto ou considerando a refatoração de um existente, experimente o HTMX. Você pode se surpreender com o quanto pode alcançar com tão pouco.
Sugestões de cursos
Descubra o caminho para se tornar um especialista em programação web. Aprenda HTML, CSS, JavaScript e os principais frameworks nesta jornada emocionante. Com instrutores experientes e materiais práticos, você desenvolverá habilidades práticas para criar sites impressionantes e aplicativos interativos. Impulsione sua carreira na indústria de tecnologia e abra portas para oportunidades de emprego lucrativas. Garanta sua vaga hoje mesmo e inicie sua jornada para se tornar um desenvolvedor web de sucesso.
Método Para Aprender a Programar do Absoluto ZERO com Node.js, React e React Native.
As tecnologias ensinadas no curso são responsáveis por muitas vagas no mercado de trabalho.
Além da alta demanda, os salários vão de R$47.000,00 até R$197.000,00 anuais tendo empresas que possibilitam o trabalho remoto e até vagas Internacionais.
Para que você possa estar apto a preencher uma dessas vagas eu vou te apresentar o passo a passo para você se tornar um verdadeiro expert nessas tecnologias.
O curso te dará o passo a passo de como criar estruturar de um sistema do zero com Node.js, React e React Native.