Controlando o estado de aplicações React com Redux e Optics

Vinicius Volpe
Aurum Tech
Published in
6 min readOct 29, 2018

Atualmente React é uma das bibliotecas Javascript que mais crescem em interesse e uso pela comunidade. Apesar de ser bem completa, esta biblioteca ainda necessita de alguns complementos, principalmente para controle de estado entre componentes. É ai que entra o Redux, uma outra biblioteca Javascript, com foco em controle de estado, que se tornou mais popular entre os usuários de React.

Neste post vou apresentar uma forma de se trabalhar com o Redux utilizando um conceito chamado Optics, uma abstração modular que permite acesso ou “alterações” em estruturas de dados imutáveis. O foco será em como alterar o estado controlado pelo Redux, portanto é necessário já ter o conhecimento de como funciona o React e como integrá-lo com esta biblioteca.

Redux

Como dito anteriormente, o Redux é um container de estado para aplicações Javascript. Com ele é possível controlar o estado de toda sua aplicação, descrevendo como este estado será atualizado em resposta aos objetos de ação, os chamados actions.

Apesar de ser muito utilizado com React, o Redux não tem nenhuma dependência com a biblioteca, e pode ser utilizado em qualquer aplicação Javascript que precise de um controle e atualização de estado. Pode ser usado até mesmo em aplicações backend com Node.js

Antes de começarmos a utilizar esta biblioteca, é necessário entender os três princípios básicos dela:

1 — Single source of truth

Todo o estado da sua aplicação é armazenado em uma árvore de objetos com um único “store”

Isto significa que todos os dados que compõem os estados da sua aplicação estarão em um único objeto chamado store. Este princípio traz vários benefícios como: facilidade de debug ou inspeção do estado, fácil acesso em toda a aplicação, e velocidade no ciclo de desenvolvimento.

2 — State is read-only

A unica forma de alterar o estado é emitindo uma ação, um objeto que descreve o que vai acontecer

Ou seja, o estado só pode ser alterado através de eventos. Estes eventos contém as informações necessárias para atualizar o estado da aplicação. Isso garante que views e callbacks não alterem diretamente o objeto de estado.

3- Changes are made with pure functions

Para especificar como a árvore de estado é alterada pelo objeto de ação, devemos utilizar funções puras

Utilizar funções puras garante que o objeto de estado nunca será atualizado e sim recriado. Uma regra básica do Redux é que a árvore de estados deve ser imutável.

Conceitos explicados, vamos ao exemplo. Imagine que você possui uma aplicação de monitoramento de serviços, e você deseja controlar o estado desta aplicação com Redux. A primeira coisa a se fazer é definir o estado inicial, neste caso vamos ter um atributo para guardar a data da última atualização, um objeto weather, que vai guardar um status e a lista de logs desta aplicação e um objeto services que vai armazenar outros objetos com os mesmos atributos status e logs.

Inicialmente vamos apenas atualizar o atributo lastUpdate dentro do state através de um objeto de ação com o type “UPDATE_LAST_UPDATE”, para isso vou utilizar o spread operator do Javascript. Agora quando um objeto de ação é emitido com este type, um novo objeto state será criado substituindo o valor de lastUpdate.

Continuando, vamos agora atualizar o status dentro de weather através de um objeto de ação com o type “UPDATE_WEATHER_STATUS”:

Como todo o estado deve ser imutável, temos que copiar todo o objeto state, o objeto weather e substituir o campo status. A medida que a aplicação cresce e as hierarquias dentro da árvore de estados aumenta, mais verboso ficará a manipulação do objeto state. Esta seria toda a função reducer no nosso exemplo:

Problema

Atualizar estruturas de dados imutáveis pode ser algo muito trabalhoso, principalmente se esta estrutura é muito complexa. No caso do Redux, quanto maior a hierarquia dentro do objeto status, mais código será necessário para atualizar esta estrutura sem modificar o objeto state. Existem diversas formas de resolver este problema: separar a atualização do state em funções específicas para cada action, ou até mesmo criar reducers separados, quebrando a estrutura em várias menores. Porém a solução que será apresentada aqui é a utilização de abstrações modulares, elegantes e reutilizáveis para atualização de estruturas de dados imutáveis.

Optics

Optics é essencialmente uma abstração que permite acessar ou atualizar estruturas de dados imutáveis de uma maneira elegante. Através desta abstração podemos criar funções para acessar um determinado dado em uma estrutura de dados complexa, ou então criar funções para construir uma nova estrutura alterando apenas uma parte dela, garantindo a imutabilidade.

Este conceito é extremamente amplo, e até um pouco complexo no início, mas para a nossa aplicação de exemplo é necessário apenas entender uma parte, as chamadas Lenses, ou lentes em português. Caso você tenha curiosidade sobre todo o conceito de Optics vou deixar links sobre o assunto no final do post.

Lenses

Lenses, ou lentes, são componentes que permitem o acesso a um determinado dado dentro de uma estrutura. Esta abstração veio dos conceitos da ótica, ou seja, as lentes são criadas com o intuito de focar em um ponto específico dos dados. Este acesso é concedido através de duas funções (view e update), que nos possibilitam ver ou alterar este dado, mantendo o resto da estrutura inalterada.

Representação gráfica de Lenses

O ponto mais interessante deste tipo de abstração é o reaproveitamento. No caso das lentes, podemos reutilizar a mesma função em diferentes estruturas e até mesmo criar novas lentes através de composições.

Para aplicar o conceito de Optics Lenses no Javascript vamos utilizar uma biblioteca chamada Vitrarius.

O primeiro passo é criar uma função que atualiza o atributo lastUpdate, vamos chamar de lastUpdateLens. Para isso basta receber o novo valor e utilizar a função inject da biblioteca Vitrarius.

Com a nossa primeira lente pronta podemos aplicar a alteração do state dentro do Redux, para isso utilizamos a função view do Vitrarius, passando o resultado da execução da nossa lente e o próprio state.

O próximo passo seria alterar o objeto weather. Vamos então criar lentes para os atributos status e logs.

Porém agora, para executar a alteração precisamos também criar uma lente para visualizar o objeto weather dentro do state. Para isso vamos utilizar a função pluck.

Lentes prontas, podemos atualizar o state, certo? Porém agora como temos duas lentes, precisamos fazer uma composição delas para que a alteração aconteça no local correto dentro da nossa estrutura de dados. Para isso é preciso utilizar a função compose.

Seguindo na nossa hierarquia precisamos alterar os objetos dentro de services. E agora vem a parte mais legal, como os atributos destes objetos são os mesmos (status e logs) podemos utilizar as mesmas lentes antes criadas, fazendo apenas a composição com os objetos da hierarquia:

Ao final das alterações, nosso reducer ficaria assim:

Conclusão

Neste post vimos uma maneira elegante de alterar as estruturas de dados imutáveis do Redux. Minha intenção ao escrever este post é despertar a curiosidade de quem lê, e mostrar que nem sempre a escolha mais óbvia é a melhor. Sempre que nos depararmos com um problema, devemos buscar diferentes alternativas para solucioná-las, evitando resolver tudo sempre da mesma forma.

E pra quem ficou curioso sobre Optics e quer se aprofundar mais no assunto, segue uma lista de materiais bacanas para começar:

--

--