Manipulando estado com Observables e Subjects usando RxJs

Manipulando estado com Observables e Subjects usando RxJs

Qualquer um que já começou a organizar uma aplicação que foi crescendo em tamanho se deparou com esse problema: Ou cada componente seu guarda seu estado e você se encontra em um mar de duplicações de valores, ou você passa prop do pai até o tataraneto só para poder acessar uma ação ou informação nos miolos dos seus componentes.

É por isso que precisamos conversar sobre “Centralização do Estado”, um dos modos de utilizar o conceito do SSOF (Single Source of Truth), e é por isso que existem bibliotecas fantásticas que realizam este tipo de trabalho, como o Flux, Redux, MobX, etc. Todas elas são ótimas libs mas dependem de você aprender uma outra arquitetura.

Se o seu caso é como o meu e do pessoal da Tegra que já trabalha com RxJs, saiba que é possível fazer a sua própria solução simples para armazenar e centralizar o estado da sua aplicação utilizando essa mesma lib.

Quando o React atualiza a interface?

É importante deixarmos bem claro que o React atualiza a interface na tela no navegador (aka Render) em duas situações:

  1. Se as Props do componente mudam.
  2. Se o Estado interno do componente muda.

Teoricamente, se você tem uma Stream de dados e, quando estes dados forem modificados, você pegar esta informação e passar como as Props do seu componente, o seu componente seria atualizado.

Para fazer esse tipo de magia, utilizamos um amigo nosso do RxJs muito útil, chamado de BehaviourSubject.

O que é o BehaviorSubject?

É meio complicado explicar o que é um Subject, mas um jeito muito prático é se você imaginar uma roda de conversa.

  1. Imagine uma roda de conversa entre amigos.
  2. Todos que estão na roda estão “inscritos” no Subject, portanto, recebem as informações que este Subject tem.
  3. Quando alguém fala: “Subject, o próximo assunto é Comida”, o Subject recebe a alteração e envia para todo mundo qual é o próximo assunto.
  4. Agora, todos sabem que o conteúdo do Subject é “Comida”
  5. Quando alguém novo chega e se inscreve no Subject, ele automaticamente recebe o valor “Comida”
  6. Alguém novamente diz: “Subject, o próximo assunto é Protecionismo Florestal”.
  7. Agora, todos, inclusive o recém chegado, recebem que o novo conteúdo do Subject é “Protecionismo Florestal”.

Traduzindo isso em código, seria mais ou menos assim (código do JsBin):

const roda$ = new BehaviorSubject('Animais');
const pessoa1 = roda.asObservable().subscribe(...);
const pessoa2 = roda.asObservable().subscribe(...);
const pessoa3 = roda.asObservable().subscribe(...);
roda$.next('Comida');
const pessoa4 = roda.asObservable().subscribe(...);
roda$.next('Protecionismo Florestal');

O BehaviorSubject faz a vez da Store

Deste modo, podemos utilizar o poder do Subject para realizar tudo o que nossa Store precisa fazer.

  • Nosso componente se inscreve (Subscribe) na Store e mapeia os dados dela para as props.
  • Quando a Store envia um novo valor (o novo estado), o componente é atualizado automaticamente
  • Quando o componente é destruido, ele faz o Unsubscribe na Store.

Criando sua Store com BehaviorSubject

Para criar uma Store que irá armazenar o estado da sua aplicação ou do seu módulo, é preciso que ela tenha alguns métodos básicos, como:

  • Criar a Store com dados iniciais.
  • Atualizar o estado
  • Pegar o estado atual

Portanto, criaremos uma Factory para que sempre que seja preciso criar uma nova Store, seja fácil como encher um copo de água na cozinha da sua empresa.

Algumas informações sobre o código acima:

  • Ele está escrito em Typescript, mas pode ser facilmente reconvertido para Javascript.
  • A const UpdateState aceita tanto um objeto literal ou uma função, assim como o setState do React faz.

Criando sua Store

Agora que temos nossa Factory, podemos criar nossa Store de uma maneira bem simples, e para isso, utilizaremos o exemplo de sempre: Um Contador.

  • Definimos uma Interface e criamos um objeto que representa os dados inicias da Store.
  • Após isso, inicializamos nossa Store e fazemos um destruct do objeto que ela nos retorna, tendo acesso ao Subject e aos handlers para atualizar e ler os dados.
  • Criamos nossas 3 ações: Incrementar, Decrementar e Resetar, todas elas atualizando o estado.
  • Definimos uma constante que contém todas estas ações
  • Exportamos nosso estado como Observable e nossas ações.

Conectando o Container Component na Store

Agora o Container Component só irá conectar as pontas de tudo:

  • Ele, ao montar, irá se inscrever na Store e colocar os dados dela no estado interno dele.
  • Os botões da interface irão chamar ações da const Actions que se encontra na store.
  • Quando as ações executarem, o componente será renderizado novamente.
  • Quando o componente for destruído, cancelamos nossa inscrição da Store.

Criando uma HOC

É possível ver que isso funcionou bem, porém, fica bem cansativo a cada Container, escrever toda essa lógica do Mount, Unmount e Subscribe, correto? Por isso que podemos fazer uma HOC (Higher Order Component) que recebe a lógica para processar mapear a Store e o Componente base, e nos retorna o mesmo componente já conectado com a Store (semelhante ao que o Connect do Redux faz)

O que fazemos aqui é o mesmo que foi feito antes, porém de uma maneira mais genérica:

  • Temos uma função que recebe dois parâmetros: O Componente e a Stream
  • O retorno dessa função é um PureComponent que, no seu DidMount, se inscreve na Stream, e ao ser destruído, cancela a inscrição da mesma.
  • Ele tem dois dados no estado dele: Se a Stream já carregou pela primeira vez e os dados que ele recebe da Stream
  • Ao receber os dados (no Subscribe), ele atualiza o seu estado interno.
  • No seu Render, ele renderiza o componente passado como parâmetro, passando os dados da Stream como Props.

E com isso, podemos simplificar ainda mais no nosso Container Component feito alí em cima:

Demo

Você pode conferir o Demo funcional com o código final em Javascript neste link no CodeSandbox.

Até o próximo,