Desenvolvimento

20 dez, 2016

Como React e Redux me fizeram um programador melhor

Publicidade

Há uns dias, no primeiro hangout do React Cast, apareceu o assunto sobre como React e Redux nos faziam ser programadores melhores. Neste artigo, vou esclarecer os pontos que me levam a acreditar nisso.

1

Quando começamos a trabalhar como programadores, é muito comum aprendermos a trabalhar com abstrações que facilitam nosso caminho e nossa curva de aprendizado. Um grande exemplo disso é o jQuery, que provavelmente é a primeira lib com que os desenvolvedores Frontend tem contato.

Abstrações são ótimas, elas podem facilitar muito as nossas vidas e aumentar nossa produtividade. Porém, é muito comum programadores ficarem presos às abstrações e se esquecerem de aprender os fundamentos básicos referentes à tecnologia abstraída. Os programadores se prendem aos vícios das abstrações e ficam completamente dependentes delas.

Existe este meme clássico que exemplifica claramente isso:

2

É importante você saber pensar por fora das abstrações, para saber quando ou não utilizá-las.

Se a única ferramenta que você tem é um martelo, para você tudo começa a se parecer com um prego.

Como é o caso de inúmeras animações ainda feitas em jQuery, mas que facilmente poderiam ser substituídas por CSS.

3

Disclaimer: Antes de continuar este artigo, deixo claro que eu trabalhei mais de um ano com Angular, em grandes aplicações, antes de começar a trabalhar com React, e ainda trabalho com ele em aplicações já em produção. Falo aqui como alguém que antes defendia muito o framework e adorava trabalhar com ele.

Trabalhando com Angular e outros frameworks, nós acabamos seguindo p0r esse mesmo caminho, de pensar como resolver problema X com o framework, antes mesmo de pensar como poderíamos resolvê-lo sem ele.

Um claro exemplo que eu poderia dar seria o angular.forEach. Nós poderíamos simplesmente utilizar Array.forEach, nativo do JavaScript.

Eu sei que há casos em que você precisa iterar sobre um objeto e não um array; para isso, bastaria utilizar Object.keys(Array).forEach, também nativo. Mas com o Angular é mais cômodo, não é?

Nós não paramos por aí, utilizamos factory, service, session, value, constant, dependency Injection, tudo isso específico do Angular. Quando precisamos fazer uma integração específica, como integrar a aplicação com um serviço de websocket, utilizamos módulos como Angular Socket IO, em vez de utilizar diretamente o socket.io-client. Aos poucos, você olha para a sua aplicação e percebe que ela está totalmente acoplada e dependente do framework.

Você pode vir com o argumento que é possível deixar a aplicação menos acoplada e que esses módulos na maioria das vezes só lidam com a natureza assíncrona do Angular. Mas tentar deixar o Angular menos acoplado, utilizando funções construtoras em vez de factories e aí por diante, implica ainda ter que implementar inúmeros workarounds ao longo do código, como: $scope.$apply para sincronizar o digest cycle. Sem contar que esse não é o Angular way de trabalhar e seria considerado uma má prática.

E mesmo contornando essas dependências de inúmeras formas, no final do dia você ainda não conseguiria rodar nenhum teste unitário sem ter o Angular como dependência, pelo menos não de uma forma viável.

Eu sei, peguei bastante no pé do Angular, mas justamente por ele ser um dos frameworks mais populares, por eu ter mais experiência com ele, e porque utilizando-o como exemplo, é fácil expor os problemas que é se amarrar a um framework.

Ter um framework que cobre de ponta a ponta da sua aplicação é ruim, porque toda a aplicação tende a ser desenvolvida da maneira dele. Se amanhã surge uma lib que resolve uma parte do problema de uma maneira melhor que o seu framework, dificilmente você vai conseguir migrar somente uma responsabilidade para a lib, pelo menos não de forma fluída.

Vemos esse mesmo tipo de opinião também no Backend. No artigo O fim da era dos frameworks full stack, o Elton Minetto fala sobre o assunto.

O problema em si não é o Angular, mas todo framework que te força a trabalhar da maneira dele e que você precisa “comprar o pacote completo” para se beneficiar apenas de uma feature ou outra.

Arquitetura desacoplada

Agora que conseguimos entender o que é um cenário totalmente acoplado, fica mais fácil compreender as vantagens de uma arquitetura desacoplada.

Separation of concerns(SoC)

4

Separation of Concerns, ou Separação de conceitos, é um termo cunhado por Edsger W. Dijkstra, em 1974.

No básico, se refere a identificar as responsabilidades que existem dentro de uma aplicação, isolá-las e delegá-las, de uma forma que fique claro o que cada parte do seu software faz e que fique claro onde deve ser feita cada coisa.

A adoção desse princípio traz inúmeras vantagens como: facilidade na manutenção, facilidade para encontrar e resolver bugs e facilidade para compreender o software como um todo.

Utilizando corretamente esse conceito, fica fácil substituir determinada parte do seu software quando necessário.

Seu software deve parecer com um brinquedo de lego, no qual você pode tirar uma peça amarela, colocar uma azul e ele continua funcionando, se limitando apenas a possuir as mesmas entradas e saídas.

Single Page Applications

Vamos tentar distinguir os aspectos e as responsabilidades dentro de uma SPA.

Nós podemos separar um SPA em três partes: State, View e Actions.

State

Todos os dados que existem dentro da nossa aplicação, desde os dados buscados por requests, até os dados definidos estaticamente no código.

View

Basicamente, é nosso HTML e CSS, que são renderizados de acordo com os dados que existem dentro da nossa aplicação.

Actions

São os eventos que ocorrem dentro da nossa aplicação, que poderão ou não alterar o State.

Podemos dividir esses eventos em duas categorias:

  • Interação do usuário: Click em botão, entrada de dados em um formulário etc.
  • IO: Eventos de rede, como início e fim de um request.

A forma como cada uma dessas partes se integram é bem simples. O State fica responsável por armazenar os dados e entregá-los para a View. A View, por sua vez, irá se renderizar de acordo com os dados que recebe do State. As Actions, ao ocorrerem, poderão causar uma alteração no State, que entrega os novos dados para View, e assim segue esse ciclo.

Essas características podem ser vistas em qualquer SPA, independentemente da tecnologia em que seja desenvolvida. O problema é que muitos frameworks deixam essas três características totalmente acopladas, não sendo possível substituir o “responsável” por cada uma.

React

Entre as três características que vimos, o React se propõe a ser o responsável por apenas uma, a View. O React não se importa em como você controla seu State ou suas Actions, ele só recebe os dados através de suas props e interage com o DOM para renderizar a View.

Redux

Ao contrário do React, o Redux é alheio à sua camada de View, ele fica responsável apenas pelo State e pelas Actions da sua aplicação, dando a você total poder de escolha em relação à View.

5

A Store do Redux disponibiliza três métodos para interação com o State através de sua API pública:

subscribe

Esse método permite que você adicione um listener à Store, que será chamado toda vez que houver uma alteração no State.

dispatch

Através desse método, você dispara as Actions.

getState

Executando esse método, você receberá o State atual. Esse método é normalmente utilizado dentro dos listeners, para capturar o State após uma alteração ter ocorrido.

Com esses três métodos, é possível integrar o Redux com qualquer lib de renderização da View.

Analisando essas duas libs, nós conseguimos enxergar a forma como as suas se complementam e se integram uma com a outra, e também conseguimos ver que, mesmo se complementando, elas conseguem trabalhar de forma independente, deixando a aplicação muito menos acoplada.

Programação funcional

O Redux é baseado em conceitos de programação funcional, como imutabilidade e funções puras. É através dessas funções que nós manipulamos o State. As funções em questão são os Reducers, que possuem a seguinte assinatura:

cod (currentState, action) => newState

Observando a estrutura acima, percebemos que não precisamos depender do Redux para escrever nossos testes unitários, uma vez que você só precisará chamar um Reducer, passando um objeto para simular o State atual e um outro objeto que será a Action, e fazer uma asserção com o valor de retorno.

O mesmo vale para o teste de Action Creators, que também devem ser funções puras.

Na documentação do Redux, há uma sessão dedicada à escrita de testes, vale a pena conferir.

Os conceitos de programação funcional que o Redux traz ajudam muito a expandir nosso conhecimento e nossa forma de pensar como escrevemos nossa lógica, além de serem porta de entrada para muita gente nesse novo paradigma.

O Redux e o React se baseiam numa arquitetura de fluxo dados unidirecional, o que nos dá um controle bem maior, e não cria dificuldades na hora de integrarmos uma nova lib ao projeto.

Ainda sobre React

O React nos induz a desenvolver nossa aplicação orientada a componentes, fazendo com que o reaproveitamento de código seja bem efetivo e tornando nossa aplicação muito mais modular.

Além disso, podemos contar com inúmeras ferramentas que melhoram nosso workflow, como o React Hot Loader, que nos concede uma experiência de desenvolvimento muito rica, e o React Storybook, que nos permite desenvolver nossos componentes de forma isolada, nos dando um controle que até então não tínhamos no Frontend.

Por que React e Redux nos fazem ser programadores melhores?

Em um resumo geral, React e Redux nos ensinam a pensar em nossas aplicações de forma modular nos induzindo a identificar e separar as responsabilidades de uma forma bem intuitiva. A facilidade que o React traz para trabalharmos com componentes nos conduz a separá-los de uma forma melhor, o que por si só já resulta em um reaproveitamento maior do código. A forma como cada lib contém a sua responsabilidade nos permite aprender a desenvolver aplicações de uma forma desacoplada. Os conceitos que o React e o Redux introduzem trazem um ganho sem tamanho para nós, desenvolvedores.