, 82 tweets, 13 min read
My Authors
Read all threads
Nessa thread, você aprenderá:

✅ Por que o Redux existe;
✅ Quais problemas ele resolve;
✅ Quais são as vantagens em utilizá-lo em uma aplicação;

Vem comigo! 👇
Ultimamente, tenho visto como algumas pessoas utilizam o redux em algumas aplicações e penso que um entendimento mais profundo sobre "por que o redux existe" traria ainda mais benefícios à esses projetos.
🔥 O padrão MVC

Nos últimos anos, estávamos presos ao MVC (Model-View-Controller).

Esse padrão era a forma em que isolávamos as responsabilidades de um app.
Temos o View, que renderiza o output na tela e escuta eventos disparados pelo usuário.

Esses eventos são enviados ao Controller, que atualiza o estado.
Já o Model, que é onde o estado é armazenado, engatilha mudanças de eventos. O View escuta essas mudanças, faz queries no estado e renderiza novamente.

Como você percebeu, há várias setas indo e voltando em direções diferentes. Esse é o problema a ser resolvido.
🔥 2-Way Data Flow Complexo

Para complicar um pouquinho mais, alguns frameworks e libs introduziram o conceito de two-way data binding, o que significa que, você tem um estado armazenado no model e algum elemento de interface de usuário que corresponde diretamente a esse estado.
Quando esse componente é atualizado, o model escuta essa modificação diretamente e responde a essa mudança, e vice-e-versa.

Ou seja, os dados fluem em ambas as direções.
🔥 Renderização não-determinística do View

O problema com o two-way data binding é que você se encontrava em situações onde a renderização do View era imprevisível.

Em alguns casos, com o view fazendo requests, o que piorava a situação.
Quando você tem um render imprevisível, você não pode dizer que tem um pedaço específico do estado que sempre produzirá um output específico.

Você pode ter race conditions e efeitos colaterais acontecendo na aplicação, e nem se dar conta disso.
🔥 A arquitetura Flux

O time de desenvolvimento do Facebook percebeu esse problema e decidiu criar uma nova arquitetura para gerenciar o estado de seus projetos.

Eles queriam abandonar o MVC, que era o padrão da indústria por décadas, e fazer algo completamente diferente.
🔥 1-Way Data Flow Simples

A ideia era bem simples: 1-Way Data Flow.

Eles basicamente pensaram "E se, dado um estado específico, pudéssemos sempre contar com o mesmo output sendo renderizado?".

Nasce o 1-Way Data Flow, que podemos representar como um circuito:
Sua aplicação tem o estado armazenado na store e, dado certo estado, a view o renderiza.

Dado um mesmo input, o output sempre será o mesmo.

A view pode disparar ações. Ações são objetos. E esse objeto irá descrever o tipo da ação.
É uma forma de dizer "esta é a intenção dessa ação em particular. Isso é o que o usuário espera que aconteça na aplicação". Ou, "o que deve acontecer como resposta à um evento em particular".

A ação vai para um "dispatcher", e esse dispatcher envia a ação para a store.
A store decide: "dado esse tipo de ação, vou executar uma modificação específica no estado".

E tudo isso acontece num fluxo unidirecional. Não há listeners indo e voltando aqui. É fácil entender exatamente o que está acontecendo quando uma ação chega à store.
🔥 Renderização determinística do View e Efeitos coleterais isolados

Agora, network requests, I/O assíncronos e cliques do usuário estão isolados da aplicação.
Eventos de UI e network events ficam isolados da renderização da view e do que está acontecendo com o estado da aplicação.

Ter efeitos colaterais isolados do restante da sua lógica de negócio é tudo o que você precisa para manter a previsibilidade em sua aplicação.
🔥 Redux

E aqui entra o Redux.

O Redux pega o padrão Flux e o leva ainda mais além.

Redux = flux + programação funcional
🔥 O que é programação funcional?

Programação funcional é você construir sua aplicação baseada em funções puras e componíveis.

E isso é o que o Redux faz com a arquitetura flux.
🔥 reducer(state, action) => newState

A forma com que o redux faz isso é através de reducers.

A maioria das coisas que você fará com o redux acontecerá em reducers functions.
Uma reducer function recebe um estado inicial e algum tipo de payload (um action object) e retorna um novo estado, baseado na ação.

Por exemplo, vamos imaginar um reducer de soma.
Iniciaremos um estado com "0", receberemos um tipo de dado e adicionaremos esse tipo de dado ao estado existente:
Porém, ao invés de arrays, o redux aplica o conceito de reducers à um stream de action objects.

Streams nada mais são do que uma sequência de objetos.
🔥 Actions

Esta é a anatomia de um action object:
A propriedade type nos diz a intenção desta ação em particular, enquanto o payload armazena alguns dados nele. Simples assim.

Vamos reimaginar nosso reducer de soma, no estilo redux:
Caso action.type seja 'ADD', retornamos o estado + o action.payload (que é apenas um número que você quer adicionar à soma).

Perceba que há dois casos diferentes neste reducer. Além do ADD case, temos um default case.
O default existe como um fallback: se nenhum "case" dá match com o action.type, o estado é retornado. Um reducer sempre retorna um estado.

Mesmo que nada seja passado como argumento para um reducer, isso deve ser um shortcut para que o estado seja retornado.
Vamos criar um array de actions e encadear a execução de um reduce(), que recebe por parâmetro o reducer que criamos:
Tenha isso em mente: a mágica do redux é utilizar reducers puros.

Se o método reduce() é familiar para você, você será capaz de aplicar esse conhecimento do redux à muitas outras coisas. Mas, por que você iria querer fazer isso?
Action object streams são como um append-only op log para bancos de dados.

É como registros de transações. Imagine que sua conta bancária mantém um registro de cada transação que já aconteceu.
Isso possibilita que você visualize o histórico dos seus gastos e detecte padrões. Você consegue visualizar todo o estado do histórico da sua conta.
Em termos funcionais, object streams facilitam muito o trackeamento de como certos bugs aconteceram. Afinal, você consegue ver exatamente como o seu estado está sendo manipulado. Cada passo do caminho.
Você consegue ver exatamente qual action disparou certa atualização do estado e visualizar o resultado dessa atualização.

Você pode dar "rewind" e "replay" em seus actions objects, o que habilita features incríveis, como o time-travel debugging.

Alguns outros benefícios:
🔥 Reprodução determinística de estado

Digamos que um usuário reportou um bug em sua aplicação.

Você possui, por outro lado, uma collection de cada action que aconteceu na sessão daquele usuário.
Você pode pegar essa lista de ações, executá-la e visualizar, passo a passo, como o estado foi atualizado. Isso facilita a detecção do momento em que as coisas deram errado.
🔥 Histórico imutável de estados

Mutabilidade em uma aplicação joga fora o seu possível conhecimento em saber como um estado chegou até o momento em que ele se encontra.
Com um histórico imutável de estados, você consegue saber exatamente como um estado chegou até aquele momento, por que você tem registros de tudo o que aconteceu.
🔥 Outros benefícios

🔶 Zero bugs de time-dependency

Ao consertar um bug em uma aplicação, talvez você perceba que há várias invocações de funções em sequência. Você tenta refatorá-las mas, ao mover uma função de um local para outro, as coisas param de funcionar de repente.
Isso acontece por que as invocações dessas funções dependem do timing em que elas são executadas.
Essas funções compartilham um mesmo estado e isso é prejudicial. Quando você elimina o estado compartilhado, esse tipo de bug não acontece e se torna muito mais fácil refatorar sua aplicação.
Com o redux, como todos os efeitos colaterais são isolados da manipulação de estado, esse tipo de bug se torna inexistente.
🔶 Undo/Redo e Time travel debugging

Quando a sua manipulação de estado é uma sequência de action objects, você pode simplesmente "rebobinar" essas ações.

Se você não manter um track de como o estado está sendo manipulado, é complicado fazer isso.
🔥 O requisito fundamental para que essas features funcionem

Para que qualquer uma dessas features funcione, é um requisito que reducers sejam a única "fonte da verdade" do estado de um app.
Se você quer gerenciar o estado com o Redux e possui outras coisas manipulando o estado, você desfez todos os benefícios do Redux. Neste caso, seria melhor não utilizá-lo.

Todo o valor do redux consiste no fato de você ter um estado reproduzível e determinístico.
Se você não precisa disso, você não precisa do Redux e não deveria usá-lo.
🔥 Reducers devem ser funções puras

Isso é um valor inegociável e, na minha inocência, pensei que todas as pessoas que usam o redux saberiam disso.
Até eu ver como elas estavam tentando criar reducers... O que percebi é que algumas pessoas não sabem o que é uma função pura, ou não sabem a importância delas.
🔷 Dado um mesmo input, o retorno sempre é o mesmo output

Uma função pura, ao receber um mesmo input, sempre retornará o mesmo output.

Isso significa que o seu reducer não deve gerar ID's aleatórios e especificá-los como payload, por exemplo, rs.
Não há como obter um estado determinístico se o seu reducer gera números aleatórios, ou chama o servidor ou tenta manipular a view diretamente.

Um reducer deve saber de nada, exceto o que você passa como argumento.
Se você precisa saber, por exemplo, o dia ou a hora que um reducer foi invocado, obtenha esse dado fora do reducer e passe ele como um argumento.
🔷 Zero efeitos colaterais

Reducers não devem disparar efeitos colaterais.
Não devem modificar qualquer propriedade de objetos ou arrays (esse é um erro que muitas pessoas também cometem). Se você manipula objetos ou arrays dentro de um reducer, essa modificação acontecerá fora do reducer.
Ao invés de modificar, prefira retornar um novo objeto ou array.

Por que isso é importante?
Qualquer tipo de concorrência ou processamento paralelo e estado mutável leva à imprevisibilidade.

A busca pela reprodução determinística de estado é a razão fundamental pela qual Flux e Redux existem.
🔥 A arquitetura Redux
A maneira com que o redux funciona é disparando ações como resposta de um evento.

Essa action vai para um dispatcher, e o dispatcher envia a ação para os reducers.

Os reducers são os únicos responsáveis por tocar/fazer o update do estado.
A view escuta essas mudanças de estado. Quando o estado muda, a view é re-renderizada e o processo começa novamente.

Perceba que todas as setas do gráfico vão em apenas uma direção. É um circuito simples e fácil de entender.
🔥 Isolando side effects
Como isolar side effects? Tendo apenas um elemento responsável por fazer chamadas para a API, network I/O, etc.
Você obtém um evento da view e ela irá disparar o evento para a store que pode ou não ter um middleware ouvindo. Este middleware irá dizer para o action creator que uma chamada para a API deve ser executada.
Quando o request da API é respondido, outra ação é criada e enviada para a store.

Essa nova ação diz "terminei o que eu estava fazendo, coloque os novos dados da API na view".

Essa não é a única forma de isolar side effects. Middlewares e action creators não são obrigatórios.
Existem side effects models como o redux-saga, que isolam eventos server-side e efeitos colaterais de outra maneira.
O ponto principal é: há apenas 1 elemento responsável pelos side effects, e ainda assim, a única forma de atualizar a view é enviar uma ação para a store e executar as funções puras (reducers) para manipular o estado, o que preserva esse circuito puro.
🔥 Separação de responsabilidades

O propósito de tudo isso é separar as responsabilidades. Você faz com que partes diferentes da sua aplicação sejam responsáveis por certas coisas.

Uma parte é responsável por renderizar a view e escutar eventos do usuário.
Uma outra parte (separada) é responsável pela comunicação com as APIs.

E uma outra parte separada é responsável por atualizar o estado.
🔥 Testes unitários

Um dos grandes benefícios do Redux é que ele simplifica os testes unitários em sua aplicação.
Se você seguir as regras acima e manter as responsabilidades separadas em diferentes módulos testáveis, as chances são de que será bem mais fácil testar sua aplicação.

Por que?

Por que não existe coisa mais fácil no mundo do que testar funções puras.
Ao testá-las, você sabe que elas não manipulam algo externo. Você não precisa se preocupar com a ordem em que elas são invocadas ou algo do tipo.

E você não precisa fazer mocking de muitas coisas.
🔥 Mocks complexos são code smells

Funções puras requerem menos mocking, por que elas funcionam isolada e independentemente.

Se você precisa fazer mockings complexos em uma aplicação, isso é um indicativo que ela possui acoplamento.
🔥 Testes simples conduzem à um código simples

Se você tem testes simples, sem mocks, em que você apenas recebe um certo input e faz asserções esperando um certo output, é muito fácil fazer a implementação desse código.
Se quisermos testar nosso summing reducer e nos assegurarmos que ele soma números passados por input, é possível criarmos um array com as actions e utilizar o reducer como argumento do reduce().
Se você tem múltiplas actions e quer testá-las de uma só vez, você não precisa invocar o reducer múltiplas vezes. Basta utilizar o reduce() e testá-las de uma vez.

No exemplo acima, testamos o valor cumulativo de múltiplas actions.
🔥 Testes end-2-end

Em adição aos testes unitários, faça testes end-2-end.

Ferramentas como o Cypress permitirão que você simule o comportamento do usuário em sua aplicação, e isso testará de forma real a integração do projeto com o banco de dados, por exemplo.
Nesta perspectiva, testes end-2-end também acabam sendo um tipo de teste de integração.
E é muito importante assegurar que essa integração está realmente funcionando em sua aplicação. Não importa se o seu reducer funciona se você esquece de adicionar um subscribe() em sua view.
🔥 A pergunta que fica

Espero que essa introdução sobre o Redux tenha esclarecido um pouco do que o Redux é, como ele funciona e como ele pode ser uma ferramenta poderosa para você evoluir como programador(a).
Afinal, ao utilizá-lo, você se verá "conduzido" a pensar de forma funcional ao desenvolver suas aplicações.
Você quer threads práticas sobre como construir uma aplicação com JS puro, utilizando o Redux?
Baixe meu eBook gratuito e com exercícios 👇

Missing some Tweet in this thread? You can try to force a refresh.

Enjoying this thread?

Keep Current with Roger Melo 💻

Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Follow Us on Twitter!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3.00/month or $30.00/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!