Site menu Programação reativa

Programação reativa

A programação reativa, nome derivado do pioneiro framework React, é um conceito que tem ganhado aceitação no desenvolvimento de UIs – interfaces de usuário, tanto na Web como fora dela. Na Web, os frameworks reativos em evidência são o React e o Vue.js. Para mobile e desktop, temos React Native, Weex e Flutter.

O conceito

Como toda boa ideia, o conceito central é muito simples. Suponha uma página Web gerada com base num template, preenchido com algumas propriedades. Se alguma propriedade muda, a página inteira é gerada novamente. Simples assim!

Isto guarda uma semelhança com a renderização de jogos 3D, onde a GPU repinta a tela inteira a cada frame. A diferença é que geração de código HTML não conta com aceleração de GPU.

Portanto, regerar a página soa ineficiente. Mas o React saiu-se com uma otimização genial: aplicar apenas as diferenças na DOM. Mexer na DOM é de longe a operação mais "cara" de um aplicativo Web. Calcular as diferenças entre as versões velha e nova do código HTML é "barato" em termos de processamento.

Sim, ainda é um pouquinho mais eficiente mexer apenas em pontos exatos da DOM, em vez de gerar representações virtuais de páginas inteiras. Porém o ganho de produtividade no desenvolvimento compensa de longe.

No desenvolvimento convencional, é preciso de alguma forma monitorar mudanças nas propriedades e invocar a atualização de UI de acordo. Os frameworks reativos fazem uso de alguns truques para que este monitoramento seja automático. A página reage automaticamente às mudanças — efeito que batiza o framework React e o conceito.

A própria reatividade permite outra otimização: quando uma propriedade é alterada, reagem apenas os componentes que dependem dela. Assumindo que a página Web seja desenvolvida segundo uma hierarquia de componentes, tipicamente a mudança de uma propriedade afeta uma porção muito pequena da página, e a detecção de mudanças só precisa verificar aquela porção.

Programação funcional

A reatividade permite tirar da estante a programação funcional. Uma função pura é aquela cujo resultado depende apenas dos argumentos. Por exemplo, podemos confiar que abs(-3) é sempre igual a 3. Por outro lado, o resultado de fopen("arquivo.txt") depende de fatores externos.

Num framework reativo, o estado da UI é uma função pura das propriedades, e o estado da UI é sempre consistente em relação às propriedades. O desenvolvedor ocupa-se de "o que deve ser mostrado", não de "como mostrar".

Um enorme desafio na programação de UI típica é manter o sincronismo entre o estado interno do aplicativo e aquilo que é mostrado na tela. É muito fácil trocar os pés pelas mãos. A reatividade elimina esta preocupação.

One-way binding

Para que a UI seja função pura das propriedades, a UI nunca deve alterar a si mesma diretamente. Suponha que o nome de um botão deva mudar quando ele é pressionado. Tipicamente, fazemos isso no próprio tratador de evento de clique. Já num framework reativo, o tratador de evento deve apenas alterar uma propriedade, de que o nome do botão dependa. É o conceito de one-way binding.

Como as alterações fluem exclusivamente em uma direção, outro problema típico de programação UI deixa de acontecer: os famigerados loops infinitos, quando um tratador de evento modifica a UI, disparando outro tratador, que modifica a UI de novo e assim por diante. Óbvio que ainda é possível "fabricar" um loop infinito de forma reativa, mas a chance disso acontecer acidentalmente é menor.

O React leva isso às últimas conseqüências. Até mesmo a digitação dentro de um campo de formulário é bloqueada, pois a rigor ela altera a DOM; o React detecta e desfaz cada alteração. Para que um campo seja "preenchível", é preciso que um tratador de evento de teclado altere alguma propriedade, que por sua vez preenche reativamente o atributo value do campo.

É uma opção polêmica. Por um lado, a abordagem do React garante consistência absoluta. Por outro lado, ela anula a implementação do browser, e parece uma interferência excessiva — afinal, campos foram feitos para serem preechidos, certo? Outros frameworks, até mesmo o Vue.js, implementam two-way binding e permitem o preenchimento "natural" de um campo, cujo valor pode refletir automaticamente numa propriedade (reatividade inversa).

Propriedades, estado interno e estado da aplicação

Até aqui temos dito que a UI é função pura de propriedades, mas é interessante expandir um pouco essa nomenclatura.

No jargão React/Vue.js, propriedades ou "props" são parâmetros imutáveis de um componente de UI. Um componente-pai passa propriedades a um componente-filho. Se o pai deseja filhos com propriedades diferentes, deve gerar novos filhos, pois a "geração" anterior é imutável.

Um componente também pode possuir dados mutáveis, que constituem o estado interno. Por exemplo, um componente-relógio teria no mínimo a hora atual como estado interno. De acordo com a "doutrina reativa", esse tipo de informação só diz respeito ao próprio componente, e não deve ser compartilhada com outros, salvo na forma de propriedades passadas a componentes-filhos.

O estado da aplicação é análogo ao Modelo da arquitetura MVC. É um dicionário de dados que não pertence a nenhum componente em especial. É o topo da hierarquia, pois presumivelmente toda a UI, bem como todas as propriedades dos componentes são em última instância derivados do estado da aplicação.

É possível armazenar o estado da aplicação num componente, que geralmente será o de nível mais alto, o "pai de todos". Porém, em aplicações não-triviais, costuma-se usar algum framework auxiliar (em geral, Redux com React e Vuex com Vue).

Nem sempre é fácil saber se determinada informação deve ser armazenada como propriedade, estado interno ou estado da aplicação. Por exemplo, um relógio poderia conter a hora atual das três formas, e funcionaria bem em qualquer caso:

Gerenciadores de estado

Já mencionados, os gerenciadores de estado Redux e Vuex são os mais utilizados. Seja quem for, ele impede a manipulação desordenada do estado. Só se pode manipulá-lo através de métodos denominados mutações.

Numa aplicação bem arquitetada, as mutações seriam análogas a transações de bancos de dados, ou a regras de negócio, ou a transições de um autômato finito.

Suponha uma lista de produtos com filtro por cor. Quando o usuário seleciona uma cor, uma mutação é invocada. Numa aplicação "correta", esta mutação teria o nome "FiltrePorCor" e receberia um único parâmetro (a cor escolhida). A mutação é relacionada a uma escolha de alto nível, e o processo de filtragem ficaria escondido no gerenciador de estado. Já numa aplicação malfeita, o desenvolvedor executaria a filtragem no tratador de evento e invocaria inúmeras vezes a mutação "MostreEsteProduto" para cada item que passa no filtro.

O Vuex é particularmente simples. Há "mutações" e "ações", sendo que estas últimas podem ter "efeitos colaterais" e código assíncrono. Por exemplo, uma chamada REST deve ser implementada como ação, que por sua vez vai invocando mutações conforme a chamada se desenrola.

O Redux tem uma arquitetura mais elaborada, embora análoga, com "redutores" e "ações". Os redutores alteram o estado, enquanto as ações podem ter efeitos colaterais. A principal diferença é o desacoplamento. Uma ação não invoca redutores diretamente; ela apenas retorna uma "ficha" descrevendo o que fez. Então, a ficha é encaminhada ao redutor apropriado conforme seu tipo.

Da mesma forma, o acoplamento entre estado geral e componentes é maior no Vue/Vuex. Os componentes Vue podem acessar o estado geral via this.$store.state e usar diretamente qualquer valor contido nele. Já o Redux desencoraja isso, preferindo que o desenvolvedor use o idioma connect(mapStateToProps).

JSX versus templates

Todo desenvolvedor é doutrinado "desde criancinha" a trabalhar em camadas independentes. No caso de desenvolvimento front-end Web, as camadas são HTML (semântica), CSS (aparência visual) e Javascript (código). Frameworks como Angular seguem esta divisão: cada componente tem pelo menos três arquivos-fonte.

O React subverteu esta lógica. Um componente React é 100% Javascript e só precisa de um arquivo-fonte. Ele gera código HTML usando Javascript e encoraja a geração dinâmica de CSS (embora isto exija um framework independente do React base). Não há templates. As justificativas são boas:

O React oferece o JSX, um dialeto de Javascript que suporta HTML diretamente no código, lembrando o PHP. É outro recurso polêmico, com apaixonados defensores e detratores. Alguns atributos HTML são renomeados no JSX por conflitarem com palavras-chave Javascript, ou por variarem entre browsers. Então poder-se-ia alegar que o "HTML" embutido no JSX é na verdade uma DSL disfarçada. Outra crítica válida é a maior dificuldade de delegar o design Web a não-desenvolvedores.

Pessoalmente, não gostei muito do JSX. O Vue.js parece ter atingido um equilíbrio nestas questões. Um componente pode ser implementado num único arquivo-fonte, e dentro dele há seções separadas de template, código e CSS (cujo escopo pode ser restrito ao componente). A DSL de template do Vue.js é simples e ergonômica; é talvez a parte mais fácil de aprender.

Roteadores, SPA e SSR

Embora isto não seja característica exclusiva dos frameworks Web reativos, outra mudança de paradigma trazida no bojo deles é o AJAX pervasivo. Por padrão, a aplicação inteira tem uma única página HTML (SPA, "single-page app") que é apenas uma casca vazia. Num browser sem Javascript, tal página não faz nada, nem exibe conteúdo relevante. É exatamente o oposto da Web tradicional.

Ainda assim, é quase sempre desejável que o usuário de um site SPA tenha a ilusão de estar navegando entre páginas HTML "de verdade", com histórico e links. A simulação de navegação é levada a cabo por um roteador. Em tese, tanto Vue quanto React podem fazer uso de qualquer roteador; na prática usa-se o Vue Router e o React Router, respectivamente.

Conteúdo 100% AJAX é tendência consolidada, mas nem sempre ele é adequado. Um problema é o "SEO", a habilidade das máquinas de busca como o Google encontrar o conteúdo do seu site, pois via de regra as máquinas de busca só enxergam HTML. Outro problema é a compatibilidade com browsers sem Javascript (e.g. se o Javascript estiver desligado por questões de segurança, ou de preferência pessoal).

Há duas alternativas ao AJAX puro: geração das páginas no servidor (SSR) e "prerendering".

O prerendering gera um HTML estático e é adequado quando o conteúdo é estático. Pode ser que o prerendering de um punhado de páginas já seja o suficiente para agradar o SEO. Qualquer servidor Web pode servir tais páginas.

Já o SSR mantém o caráter dinâmico das páginas, executando Javascript no lado servidor, o que implica o uso de servidores Node.js (ou AWS Lambda, ou similar).

Tanto o ecossistema React quanto o Vue.js oferecem ferramentas de SSR e prerendering. Uma aplicação que pretenda ser SSR deve ser construída com isto em mente, ou sofrer algumas modificações.