Redux é uma biblioteca utilizada para gerenciar o state(estado) de uma aplicação, ideal para Single Page Applications (SPA), mas pode ser usado em qualquer aplicação.
Apesar de muito se falar do uso dele com React, Redux é agnóstico a framework, ou seja, você pode utilizar junto com qualquer outro framework ou apenas com javascript puro.

É inspirado na arquitetura Flux criada pelo Facebook, e também é inpirado em alguns conceitos da programação funcional. A comunicação entre os elementos da aplicação se torna unidirecional, o state inteiro de sua aplicação está representado em um único objeto javascript, que pode conter uma árvore de objetos aninhados ou não, este state é imutável, as mudanças devem ser realizadas através da geração de um novo state de acordo com a ação desejada.

Quando utilizei pela primeira vez, assim como no Flux, eu não gostei muito do Redux, devido a ter que escrever muito overhead de código, mas recomendo para aplicações que demandam muitas interações, compartilham dados em diferentes partes da aplicação, requerem mudanças de dados frequentemente, no geral uma aplicação não muito trivial, pois utilizar Redux acaba sendo um pouco custoso para aplicações muito simples, devido a ter um overhead de codificação para seguir suas características.

A seguir algumas características importantes do Redux:

  • O fluxo de comunicação é unidirecional;
  • O state inteiro de sua aplicação está centralizado num único lugar, o que também facilita a normalização dos dados;
  • O state nunca muda diretamente ele é imutável, mudanças são disparadas(dispatch) por ações(actions) e manipuladas por funções puras(reducers), que retornam uma cópia do state com as alterações desejadas;
  • O código é estruturado seguindo as características já citadas, o que facilita o entendimento da equipe de desenvolvimento que já está habituada com este "padrão".

Redux é basicamente constituído de 4 itens: Actions, Reducers, Middlewares e Store. A seguir irei comentar um pouco de cada um:

Actions

Actions são representadas por um objeto literal que descreve que ação ocorreu e quais dados foram alterados, são disparadas pelo metodo dispatch da store (store.dispatch(action)), podem ser invocadas por eventos da interface, rede, timers, ações do usuário ou outra fonte que tenha intenção de manipular os dados de alguma forma.
As actions devem ter uma propriedade chamada type que indica que tipo de ação foi disparada. Redux só exige que a action tenha um type , mas existe um padrão para actions que podemos seguir: Flux Standard Action.
Seguindo este padrão, as propriedades permitidas para uma action são type, payload, error e meta.
Uma action DEVE:

  • Ser objeto javascript literal;
  • Ter a propriedade type: uma constante string que identifica a ação.
Uma action PODE ter:
  • Propriedade error: se for true a action é considerada uma ação de erro, e a informação de erro deve ser definida no payload;
  • Propriedade payload. Pode ser qualquer valor que represente o payload da ação, os dados que representam esta ação. Se error for true o payload deve ser um objeto Error;
  • Propriedade meta: Pode ser qualquer informação extra não contida no payload.
Podemos criar atalhos para gerar nossas actions, isso é chamado de Action Creator, que nada mais é uma função que retorna uma action.
Caso a action creator necessite ler o state atual da aplicação, chamar uma API, chamadas de rotas, então esta action deve retornar uma function, que adiará a execução da action, onde nestes casos são gerenciadas pelos middlewares, como por exemplo redux-thunk .

Exemplo de actions:

Reducers

Reducers são funções puras responsáveis pela mudança do state da aplicação, como state deve ser imutável, nos reducers devemos criar uma cópia do state com as mudanças desejadas. Podemos ter vários reducers, cada um gerenciando um pedaço do state da aplicação.
Um reducer recebe por parâmetro o state atual da aplicação e uma action, e retorna o novo state, não devem ter chamadas impuras, dado um input ele deve retornar o mesmo output esperado, não devem alterar variáveis fora do escopo, não fazem chamadas as APIs, não devem ter side effect . O retorno da execução de todos os reducers será o novo state da aplicação a cada action disparada, ou seja, toda vez que é disparado uma action, todos os reducers são executados e assim é calculado o novo state da aplicação.
O Redux possui uma função chamada combineReducers que facilita a combinação dos reducers gerando um único reducer, necessário quando temos vários reducers que gerenciam partes diferentes do state.

Exemplo de reducer: Obs: perceba que utilizei a sintax Object Spread Operator (...state) para criar uma cópia do state

Middlewares

Middlewares são funções que interceptam uma action antes dela chegar aos reducers e depois que é obtido uma novo state. Middlewares são muito úteis, podem ser utilizados para ações assíncronas, logs, chamadas de API, monitorar mudanças do state, colocar um delay na execução da action, e etc...
Para colocarmos os middlewares dentro da store devemos utilizar a função applyMiddleware do Redux.

Estrutura de um middleware:
({ getState, dispatch }) => next => action => next(action);

  • Recebe getState, e dispatch da store, assim é possível obter o state(getState) e também poder disparar(dispatch) outras actions;
  • Para encaminhar a action para outros middlewares até atingir os reducers devemos usar o next passando a action;
  • O retorno do middleware é o que será retornado na chamada do dispatch que iniciou a action, logo abaixo nos exemplos de middlewares fiz um middleware de delay(colocarDelay) que retorna uma function para cancelar o delay caso necessário.

Exemplos de como criar Middlewares:

Middlewares bastante usados na comunidade Redux: redux-thunk, redux-logger, redux-saga.

Store

Diferente do Flux, no Redux só temos uma store, que é responsável por gerenciar o state da aplicação, orquestra todo o fluxo de comunicação, oferece métodos para obter o state, escutar e disparar ações.
Oferece os seguintes métodos:

  • getState(): retorna o state;
  • dispatch(action): dispara a action;
  • unsubscribe = subscribe(fn): registra uma função(fn) que ficará ouvindo as mudanças do state, ou seja ela será executada toda vez que o state mudar;
  • Para fazer o unsubscribe, ou seja parar de ouvir as mudanças de state, devemos executar a function que o subscribe retorna.
Para criar a store utilizamos a função createStore do Redux.

Criando a store:

Fluxo de dados

O fluxo de dados é unidirecional, e é gerenciado por apenas 1 store, que orquestra todo o fluxo de comunicação dos dados da aplicação.
Tendo a store criada e os ouvintes de mudança de state configurados, o fluxo funciona da seguinte forma:

  • É disparado uma action através do método dispatch da store, contendo os dados que foram alterados e o tipo de ação realizada. Normalmente quem incia esta action é algum evento gerado pela View da aplicação;
  • A store então encaminha esta action para os middlewares, estes que por sua vez podem fazer outras chamadas (async, ajax, timers, logs) e dispararem outras actions caso necessário;
  • Em seguida a store encaminha a action para os reducers, que serão responsáveis em retornar o novo state da aplicação baseado na action recebida e o state atual;
  • Após a execução de todos os reducers, é obtido um novo state, então a store notifica os ouvintes que houve uma alteração de state;
  • Os ouvintes que normalmente são os componentes das Views, renderizam novamente com os dados atualizados

Todo este fluxo pode ser representado pelo seguinte diagrama:

Indo além

Redux ajuda muito a gerenciar o state de sua aplicação de um maneira simples e organizada, eu só falei sobre o Redux, mas quando utilizado com React temos algumas facilidades através do react-redux que traz algumas facilidades para tornar o desenvolvimento com redux mais simples ainda. No proximo post talvez eu fale sobre react-redux.
Outra coisa interessante são alguns conceitos de programação funcional, o ideal é você se aprofundar nestes conceitos, Redux é muito influenciado pelos conceitos da programação funcional.
Uma das coisas que achei legal do Redux é que a comunidade javascript criou algumas ferramentas de debug muito interessantes, recomendo que deem uma olhada no redux-devtools é muito legal :)

Referências