Descubra como usar Redux sem React

Renato Vieira
CITi
Published in
7 min readFeb 10, 2018

Neste artigo, veremos:

  1. Uma breve introdução ao conceito do Redux e qual problema ele visa resolver;
  2. Como utilizar o Redux em uma aplicação web simples, sem o uso do React;
  3. Como estender o uso do Redux para aplicações que realizam operações assíncronas.
Redux (fonte: https://redux.js.org/)

Redux pode ser definido como uma forma de facilitar o desenvolvimento de aplicações web seguindo a metodologia do Flux, uma arquitetura proposta pelo Facebook para gerenciar o estado de aplicações React.

A biblioteca, (co-)criada por Dan Abramov, tem como ideia principal a separação de responsabilidades entre a parte visual e o gerenciamento do estado, estando a segunda parte sob responsabilidade do Redux.

Um outro ponto importante é o fluxo dos dados da aplicação, que segue um "caminho" único, tornando a aplicação mais fácil de ser testada e debugada. Os conceitos são ilustrados na imagem abaixo e serão melhor explicados ao longo do artigo.

Fluxo de dados no Redux (fonte: AngularFirebase)

Apesar de constantemente ser usado em conjunto com o React, o Redux pode ser aplicado com qualquer biblioteca de interface gráfica, sendo esta implementação o nosso objetivo ao longo do texto.

Hora de implementar

Nosso objetivo ao longo do post será implementar uma aplicação web que permite ao usuário digitar uma palavra em um campo de busca e armazenar os resultados da requisição em uma lista que ficará visível na tela. Além disso, o usuário poderá ver qual foi a última busca realizada. Todo o fluxo dos dados da aplicação será gerenciado pelo Redux e utilizaremos jQuery para facilitar a manipulação do DOM e atualizar a parte gráfica.

Para tal, criamos um markup para o projeto da seguinte forma:

index.html

Note que ao fim do <body> estamos importando uma série de arquivos Javascript, visando garantir uma boa modularidade ao projeto. Inicialmente importamos o script que nos fornece as funcionalidades do Redux, com os cinco arquivos seguintes sendo responsáveis por utilizar tais funcionalidades para gerenciar o estado da aplicação. Por fim, importamos o jQuery e o nosso arquivo de script central para tratar as interações do usuário com a interface.

Começaremos implementando o arquivo api.js, que será responsável por, dado uma string que representa o nome de algum personagem, retornar um JSON que contém um atributo results que representa uma lista de JSONs que possuem um atributo name que representa uma string (estamos adotando esse schema um tanto quanto complexo para facilitar nossa vida quando formos refatorar o projeto na segunda etapa deste post). O arquivo deverá ficar próximo disto:

api.js

Em seguida, implementamos o arquivo actions.js, que conterá tanto os tipos das ações que nossa aplicação será capaz de tratar, bem como as funções que as geram (chamadas formalmente de action creators). Uma ação nada mais é do que um objeto Javascript que contém um tipo e um conteúdo que será processado mais à frente.

Os tipos de ações são importantes pois servem como base para escolher qual procedimento adotar quando estivermos tratando-a. Por uma questão de escolha, encapsularemos os tipos dentro de um JSON para facilitar o uso posterior.

actions.js

O próximo passo consiste em implementar os reducers da aplicação. Um reducer é uma função de Javascript que recebe dois parâmetros, o estado atual e a ação que foi enviada, como descrito abaixo.

reducers.js

Falamos bastante sobre o estado da aplicação mas até o momento não foi dada uma explicação sobre o que ele é concretamente. O estado é um objeto Javascript, composto por chaves e valores, que guarda diversas informações acerca do sistema e que é atualizado pelos reducers usando os dados passados por uma ação.

É importante salientar que é imprescindível que um reducer seja implementado como uma função pura, ou seja, para um mesmo conjunto de entradas, a saída da função deve ser a mesma e os parâmetros de input não devem ser modificados no corpo da mesma. Perceba que estamos usando métodos como concat e filter para operar sobre o estado atual, visto que esses métodos não alteram o objeto que os invoca, mas sim gera um objeto completamente novo. A imutabilidade do estado permite implementar funcionalidades tais como a reversão para um estado anterior.

Um reducer receberá como entrada apenas o pedaço do estado o qual ele é responsável por tratar e deverá decidir, com base no tipo da ação, se o estado deve ser atualizado. A checagem para verificar se o estado não foi inicializado é realizada especificamente para tratar a primeira chamada deste reducer, quando os estados ainda possuem valores undefined.

Se o reducer não for responsável por tratar tal ação, ele deve simplesmente passar o estado da entrada adiante (note a presença de um default nos switches do código acima).

store.js

Criaremos agora um arquivo store.js, que servirá para declararmos propriamente dito o gerenciador de estado. Para isso, criamos uma variável que vai armazenar o nosso reducer principal. Perceba que estamos usando a função combineReducers para criar um único reducer a partir dos dois que implementamos anteriormente.

Cada um deles será indexado por uma chave que representará o estado a ser tratado por cada um deles quando uma ação for disparada.

A chamada do método subscribe tem a intenção apenas de exibir no console do browser o estado da aplicação toda vez que uma ação for processada pelos reducers na tentativa de criar um novo estado.

O último arquivo a ser criado é o script para tratar parte gráfica da aplicação. Como foi dito anteriormente, utilizaremos o jQuery como apoio.

script.js

As duas funções que implementamos inicialmente (renderLastSearch e renderList ) serão responsáveis por renderizar o conteúdo do estado atual, a primeira mostrando a última busca e a segunda a lista de personagens propriamente dita. Utilizamos novamente o subscribe para executar essas funções quando o estado for atualizado.

Por fim, criamos os métodos que capturam os eventos de interação com o DOM. Quando desejamos adicionar um personagem, ao clicar no botão apropriado, capturamos o valor que está no input do HTML e invocamos as duas ações que criamos anteriormente passando o nome como parâmetro.

O envio das ações é realizado através do método dispatch , que faz parte do objeto store que criamos anteriormente. Basta que passemos como parâmetro a função que cria a ação desejada, conforme havíamos feito anteriormente.

Fazemos o mesmo no evento que captura um clique no botão de remover um personagem da lista, obtendo o valor da propriedade customizada key que definimos na hora de adicionar um personagem e o passando para a ação de remover um personagem da lista.

Parabéns, você acabou de escrever a sua primeira aplicação usando Redux!

Trabalhando com requisições assíncronas

Suponha agora que, ao invés de obter um personagem através de uma função do próprio Javascript, você deseja que os dados sejam recebidos de um servidor, que pode ser acessado via REST API. Para isso, utilizaremos a Star Wars API para obter os personagens e a biblioteca axios para realizar as chamadas (caso deseje, você pode também usar a Fetch, que é um biblioteca nativa do Javascript e tem comportamento similar à axios).

Para adicionar a axios ao seu projeto, basta adicionar esta tag de script ao seu HTML.

<script src="https://unpkg.com/axios@latest/dist/axios.min.js"></script>

Em seguida, altere o seu arquivo api.js para que ele passe a utilizar a axios para realizar as chamadas do servidor. O código deverá ficar similar ao abaixo:

api.js

Toda chamada que a axios faz retorna uma Promise, que resumidamente é um objeto utilizado para processar operações assíncronas. Ou seja, o conteúdo pode não estar (e provavelmente não estará) disponível de forma imediata no seu código. Utilizamos o método then de uma Promise para poder obter a resposta que o servidor nos envia, quando ela estiver pronta.

Será necessário alterar, também, a função que cria a ação de adicionar um personagem à lista. Você poderia pensar que bastaria, então, modificar a função para algo similar a isto:

actions.js

Se você clicar no botão de busca usando essa implementação, verá a seguinte mensagem no console do navegador:

Ops…

O erro ocorre pois a função dispatch do objeto store que estamos usando no script.js espera como parâmetro um objeto JSON, e não uma função como estamos tentando passar. Ele também nos dá a dica de que precisaremos utilizar algo chamado middleware para resolver o problema. Middlewares são funções auxiliares que permitem que outros tipos de objetos sejam passados como parâmetro.

Existem várias bibliotecas que utilizam abordagens diferentes para solucionar esse problema, como a redux-saga e a redux-thunk, esta última a que iremos utilizar. Primeiramente, devemos importar a biblioteca no nosso HTML usando:

<script src="https://unpkg.com/redux-thunk@latest/dist/redux-thunk.min.js"></script>

Em seguida, em store.js, faremos as seguintes modificações:

store.js

Na primeira linha, criamos uma variável que utiliza o retorno da função applyMiddleware para criar um novo middleware usando o redux-thunk. O método withExtraArgument permite que injetemos um objeto no middleware para podermos utilizá-lo posteriormente (no caso, estamos passando nosso objeto da API como parâmetro).

Por fim, na linha 2, estamos adicionando o middleware como parâmetro adicional de createStore para que o Redux possa utilizá-lo para processar as ações.

Faremos agora a correção do actions.js para que ele possa utilizar o redux-thunk como middleware propriamente.

actions.js

Perceba que em relação à tentativa anterior, agora estamos retornando uma nova função anônima, que recebe até 3 parâmetros: dispatch , getState e extras , onde os 2 primeiros são funções que servem como atalhos para os métodos de mesmo nome da store e o terceiro é o objeto que injetamos no middleware anteriormente.

Testando a aplicação, vemos que, agora, o comportamento é o esperado inicialmente e estamos obtendo dados diretamente de um servidor.

O código final está disponível neste repositório, com algumas modificações em relação à implementação (principalmente no uso da sintaxe de ES6 em vários trechos do código). O resultado final, contudo, é idêntico ao implementado aqui.

Nos vemos no próximo post! 🐽

Links úteis:

--

--

Renato Vieira
CITi
Writer for

Adora tecnologia e escreve aleatoriedades nas horas vagas.