Como Redux funciona?

Rafael Antonio Lucio
rafaelantoniolucio
Published in
12 min readDec 27, 2017

--

Artigo original escrito por: Dave Ceddia neste link https://daveceddia.com/how-does-redux-work/

Depois de aprender um pouco sobre React e entrar no Redux, é realmente confuso como tudo funciona.

Actions, reducers, action creators, middlewares, funções puras, imutabilidade…

A maioria destes termos parecem totalmente desconhecidos.

Neste post nós vamos desmistificar como Redux funciona, vamos dar um passo para trás que eu acho que nos ajudará a entendê-lo melhor.

Se você ainda não tem certeza sobre o que é o Redux ou porque você deve usá-lo, leia esta publicação primeiro e volte e continue lendo este post.

Primeiro vamos falar sobre o Estado

O conceito de Estado vem da tal “Máquina de Estado Finita” ou (FSM-Finish State Machine) ou autômato finito é um modelo matemático usado para representar programas de computadores ou circuitos lógicos. O conceito é concebido como uma máquina abstrata que deve estar em um de um número finito de estados.

Iremos inicial com um exemplo de alteração de estado do React, e depois adicionar o Redux peça por peça.

Aqui temos um exemplo de contador, (desconsiderei o CSS para manter isso simples)

Como uma revisão rápida, veja como isso funciona:

  • O estado inicial de count é armazenado no componente no nível superior
  • Quando o usuário clica no botão “+” o onClické chamado, que é vinculado ao método increment
  • O método increment atualiza o estado com um nova contagem
  • Porque o estado foi alterado o React re-renderiza o componente (e seus filhos), e um novo valor de contagem é exibida.

Se você precisa de mais detalhes sobre como as mudanças de estado funcionam, leia A Visual Guide to State in React e então volte e continue daqui. Sério se o código acima não esta claro pra você, você precisa saber como as mudanças de estado do React funcionam antes de aprender Redux.

Então sem mais delongas vamos a configuração básica da nossa app. Se você quiser seguir com o código, crie um projeto agora:

  • Instale o create-react-app se você não tem ele npm install -g create-react-app
  • Crie um projeto: create-react-app redux-intro
  • Abra src/App.js e substitua por isso:
  • Crie o src/Counter.jscom o exemplo de código acima
  • Abra o src/index.js e substitua por isso:

Redux

Conforme discutido na Parte 1, o Redux mantém o estado do seu aplicativo em uma única store . Então, você pode extrair partes desse estado e conectá-los em seus componentes como propriedades. Isso permite que você mantenha os dados em um lugar global (a store) e alimenta-los diretamente para qualquer componente no aplicativo, sem a ginástica de passar props em vários níveis.

Muitas vezes você verá as palavras state e store usadas interativamente. Tecnicamente, o state é o dado, e a storeé onde ele é mantido.

Ao seguir as etapas abaixo, siga o seu editor, isso irá ajudá-lo a entender como isso funciona (e nós vamos trabalhar juntos com alguns erros juntos)

Bora adicionar o Redux ao projeto:

npm install --save redux react-redux

redux vs react-redux

Espera, duas bibliotecas? “O que é react-redux” você sabe? Bem, eu tenho mentido pra você.

Veja reduxdá-lhe uma store , e permite que você:

  • Mantenha o estado.
  • Obtenha o estado.
  • Responda quando o estado muda.

Mas o reduxfaz isso tudo o que faz o react-redux?. Na verdade é o react-redux que permite conectar partes do estado aos componentes React. Isso mesmo o redux não sabe nada sobre React.

Essas bibliotecas são como duas ervilhas em um pote. 99,999% do tempo, quando alguém menciona “Redux” no contexto de React, eles estão se referindo a ambas as bibliotecas em conjunto. Portanto, tenha isso em mente quando você vê o Redux mencionado em StackOverflow, ou Reddit ou em outro lugar.

Ultimas coisa

A maioria dos tutoriais começa criando uma store , configurando o Redux, redigindo um reducer e assim por diante.

Eu vou tomar uma abordagem voltando um passo atrás, e será preciso código para fazer as coisas aparecerem na tela, mas espero que a motivação por trás de cada passo seja mais clara.

Voltando ao Redux

De volta ao aplicativo Counter, vamos imaginar por um segundo que mudamos o estado do componente para Redux

Vamos remover o estado do componente, já que obteremos isso do Redux em breve:

Conectando o Counter

Note que {this.state.count} foi modificado para {this.props.count} isto ainda não funcionará, é claro, porque o Counter não esta recebendo a propriedade count. Vamos usar o Redux para injetar isso.

Para obter a contagem do Redux, primeiro precisamos importar a função de conexão na parte superior:

import { connect } from 'react-redux'

Então precisamos conectar o componente Counter ao Redux na parte inferior, mas antes, para isso utilizaremos Containers Components.

Containers Components tem a responsabilidade de resolver toda a lógica externa ao componente e em seguida processar o subcomponente.

Isso irá lançar um erro (falaremos ele mais pra frente)

Agora utilizamos o connect para conectar o Redux com o componente Counter.

Porque connect ?

Você pode notar que a ligação parece paquena… estranha. Porque connect(mapStateToProps)(Counter) e não connect(mapStateToProps, Counter) ou connect(Counter, mapStateToProps) ? O que esta fazendo?

É escrito assim porque connect é uma higher-order function, que é uma maneira elegante de dizer que retorna uma função quando você a chama. Ao chamar essa função com um componente, retorna um novo componente (envolvido).

Nos casos onde utilizamos os containers um outro nome para ele é um higher-order component (aka “HOC”). HOCs São componentes que retornam outros componentes (envolvidos).

O que faz o connect é ligar para o Redux, retirar o estado inteiro e passá-lo através da função mapStateToProps. Isso precisa ser uma função personalizada porque somente você conhecerá a “forma” do estado no Redux.

connect passa todo o estado como se dissesse: “Ei, me conte o que você precisa com essa confusão confusa”.

O objeto que você retorna do mapStateToProps é alimentado em seu componente como propriedades (props).

O exemplo acima passará state.count como o valor da prop count : As chaves do objeto se tornam nomes de propriedades e seus valores correspondentes se tornam os valores dos propriedades. Então, você vê, esta função define literalmente um mapeamento do estado em propriedades.

Sua sensação agora deve algo como a imagem abaixo (rsrs)

Erros

Could not find “store” in either the context or props of “Connect(Counter)”

Uma vez que o connect tira os dados da store e não configuramos uma Redux store ou informamos ao aplicativo como encontrá-lo, esse erro é bastante lógico. Redux não tem idéia do que esta acontecendo agora.

Fornecendo uma Redux store

O Redux mantém o estado global para todo o aplicativo e envolvendo todo o aplicativo com o componente Providerdo react-redux, todos os componentes da árvore do aplicativo poderão usar a conexão para acessar a storedo Redux se assim o desejar.

Isso significa que a App e filhos de App (como Counter) e filhos de seus filhos, e assim por diante, todos agora podem acessar a store do Redux, mas, apenas se eles são explicitamente envolvidos pelo connect para se conectar. Não estou dizendo para conectar cada componente, seria uma má idéia (design desarrumado e lento também).

O Providerpode parecer uma magia total agora. É um pouco; ele realmente usa o recurso “contexto” do React sob o capô.

É como uma passagem secreta conectada a cada componente, e o uso da conexão abre a porta para a passagem.

Imagine merter xarope em uma pilha de panquecas, e como ele consegue entrar em TODAS as panquecas, mesmo que você simplesmente derramou na parte superior. O Provider faz isso com o Redux.

Em src/index.js, importe o Provider e envolva o conteúdo do Appcom ele.

Nós ainda continuamos teaderendo erro, é porque o Providerprecisa de uma storepara trabalhar.

Criar uma store

O Redux vem com uma função útil que cria store's, e isso é chamado createStore. Sim. Vamos fazer uma store e passá-lo para o Provider:

Outro erro, mas diferente desta vez:

Expected the reducer to be a function.

Então, aqui está um assunto sobre Redux: não é muito inteligente. Você pode esperar que ao criar uma loja, isso lhe daria um valor padrão inicial para o estado dentro dessa store. Talvez um objeto vazio?

Mas não: Redux faz zero pressuposições sobre a forma do seu estado. Você decide! Pode ser um objeto, um número ou uma string, ou o que você precisa. Portanto, temos que fornecer uma função que retornará o estado. Essa função é chamada de reducer(veremos por que em um minuto). Então, vamos fazer o mais simples possível, passá-lo para createStoree veja o que acontece:

O reducer deve sempre retornar algo.

O erro é diferente agora:

Cannot read property ‘count’ of undefined

Está quebrando porque estamos tentando acessar state.count, mas o estado é indefinido. O Redux esperava que nossa função redutora retornasse um valor para o estado, exceto que (implicitamente) retornou indefinido. As coisas estão corretamente quebradas.

É esperado que o reducerdeve receba o estado atual e retornar um novo estado, mas nunca importa; voltaremos a isso.

Vamos fazer o reducer retornar algo que corresponde à forma que precisamos: um objeto com uma propriedade de count.

Ei! Funciona! A contagem agora aparece como “42”. Impressionante.

Apenas uma coisa: o count será sempre 42.

A história até agora

Antes de entrar em como atualizar o contador, vejamos o que fizemos até agora:

  • Nós escrevemos uma função mapStateToProps que faz o que o nome diz: transforma o estado Redux em um objeto contendo propriedades.
  • Nós conectamos a store do Redux ao nosso componente Counter com a função connectdo react-redux, usando a função mapStateToProps para configurar como a conexão funciona.
  • Criamos uma função redutora para dizer ao Redux como nosso estado deveria ser.
  • Utilizamos a função createStore engenhosamente chamada para criar uma store e passamos o reducer.
  • Nós embalamos todo o nosso aplicativo no componente Providerque vem com react-redux, e passou a nossa storecomo suporte.
  • O aplicativo funciona perfeitamente, exceto o fato de que o contador está preso às 42.

Comigo até agora?

Interatividade (Fazendo-o funcionar)

Até agora isso é muito coxo, eu sei. Você poderia ter escrito uma página HTML estática com o número “42” e 2 botões quebrados em 60 segundos, mas aqui está, lendo como complicar demais essa mesma coisa com React e Redux e quem sabe o que mais.

Eu prometo que esta próxima seção fará tudo valer a pena.

Na verdade não. Retiro o que eu disse. Um simples app contador é uma excelente ferramenta de ensino, mas o Redux é absolutamente exagerado para algo assim. O estado do React está perfeitamente bom para algo tão simples. Heck, mesmo o JS simples funcionaria muito bem. Escolha a ferramenta certa para o trabalho. Redux nem sempre é essa ferramenta. Mas eu divago.

Estado inicial

Então precisamos de uma maneira de dizer ao Redux que altere o Counter.

Lembre-se da função redutora que escrevemos?

Lembre-se de como mencionei que leva o estado atual e retorna o novo estado? Bem, eu menti. Ele realmente leva o estado atual e uma ação, e então ele retorna o novo estado. Nós devemos escrever assim:

A primeira vez que Redux chama essa função, ela passará indefinida como o estado. Essa é a sua sugestão para retornar o estado inicial. Para nós, provavelmente é um objeto com uma contagem de 0.

É comum escrever o estado inicial acima do redutor e usar o recurso de argumento padrão do ES6 para fornecer um valor para o argumento do estado quando ele é indefinido.

Experimente isso. Ainda deve funcionar, exceto que agora o contador está preso em 0 em vez de 42.

Action

Estamos finalmente prontos para falar sobre o parâmetro de action. O que é isso? De onde isso vem? Como podemos usá-lo para mudar o maldito contador?

Uma “action” é um objeto JS que descreve uma mudança que queremos fazer. O único requisito é que o objeto precisa ter uma propriedade de type, e seu valor deve ser uma string. Aqui está um exemplo de action:

{ type: "INCREMENT" }

Aqui está mais um:

{ type: "DECREMENT" }

As engrenagens estão virando na sua cabeça? Você sabe o que vamos fazer depois?

Responda às actions

Lembre-se do trabalho do reducer é levar o estado atual e uma actione descobrir o novo estado. Então, se o reducer recebeu uma action como { type: "INCREMENT"}, o que você pode querer retornar como o novo estado?

Se você respondeu algo assim, você está no caminho certo:

É comum usar uma instrução switch com casos para cada action que você deseja manipular. Mude o seu reducer para se parecer com isto:

Sempre devolva um estado!

Você notará que sempre há o caso de retorno onde tudo o que faz é retornar o estado. Isso é importante, porque o Redux pode (vai) chamar seu reducer com actionsque não sabe o que fazer. Na verdade, a primeira actionque você receberá será {type: "@@redux/INIT"}. Tente colocar um console.log (ação) acima do switch e veja.

Lembre-se de que o trabalho do reducer é retornar um novo estado, mesmo que esse estado seja inalterado. Você nunca quer passar de “ter um estado” para “estado = undefined”, certo? Isso é o que aconteceria se você deixasse o caso padrão. Não faça isso.

Nunca mude o estado

Mais uma coisa para nunca fazer: não mude o estado. O Estado é imutável. Você nunca deve mudá-lo. Isso significa que você não pode fazer isso:

Você também não pode fazer coisas como state.foo = 7, ou state.items.push(newItem).

Pense nisso como um jogo onde a única coisa que você pode fazer é retornar {…}. É um jogo divertido. Enlouquece um pouco no início, mas você vai melhorar com a prática.

Todas estas regras …

Sempre devolva um estado, nunca mude de estado, não conecte todos os componentes, coma seu brócolis, não fique fora do passado 11 … é cansativo. É como uma fábrica de regras, e nem sei o que é isso.

Sim, o Redux pode ser como um pai dominador. Mas vem de um lugar de amor. Amor de Programação funcional.

Redux é construído sobre a idéia de imutabilidade, porque o estado global mutante é a estrada para a ruína.

Você já manteve um objeto global e usou isso para passar o estado em torno de um aplicativo? Isso funciona bem, legal e fácil. E então, o estado começa a mudar de formas imprevisíveis e torna-se impossível encontrar o código que está mudando.

O Redux evita esses problemas com algumas regras simples. O estado é somente leitura, e as ações são a única maneira de modificá-lo. As mudanças acontecem de um jeito e de uma maneira: action -> reducer -> novo estado. A função reducer deve ser “pura” — não pode modificar seus argumentos.

Existem ainda pacotes de complemento que permitem que você registre todas as ações que ocorrem, rebobinam e reproduzam, e qualquer outra coisa que você possa imaginar. O debugging de viagem no tempo foi uma das motivações originais para a criação do Redux.

De onde são as actions?

Uma parte deste enigma permanece: precisamos de uma maneira de alimentar uma action em nossa função redutora para que possamos incrementar e decrementar o contador.

As actions não nascem, mas são despachadas, com uma função útil chamada dispatch.

A função dispatch é fornecida pela instância da store do Redux. Ou seja, você não pode simplesmente import {dispatch}e estar a caminho. Você pode chamar store.dispatch(action), mas isso não é muito conveniente, já que a instância da storeestá disponível apenas em um arquivo.

Além de injetar o resultado do mapStateToProps como propriedades, conecte também a função de dispatch como suporte. E com esse pouco de conhecimento, podemos finalmente recuperar o contador.

Aqui está o componente final em toda a sua glória. Se você acompanhou, as únicas coisas que mudaram são as implementações de incremento e decréscimo: eles agora chamam o suporte de dispatchpassando uma action.

O código para todo o projeto (todos os dois arquivos dele) pode ser encontrado no Github.

Stateless component

Como o componente já não manipula mais o estado diretamente podemos isolar a lógica para dispachar as actions no container, lembram da responsabilidade dos containers?

Containers Components tem a responsabilidade de resolver toda a lógica externa ao componente e em seguida processar o subcomponente.

Vamos implementar o componente Counter da seguinte forma:

Vamos entender o que esta acontecendo aqui:

  • Criamos uma constante mapDispatchToProps que é uma função anonima que recebe um parâmetro chamado dispatch , que por sua vez ela retorna um objeto onde cada chave do objeto recebe uma outra função anonima que tem uma única responsabilidade “dispachar uma action”
  • Cada chave do objeto mapDispatchToProps será mapeada no connect e passará para o componente como propriedade, assim como o mapStateToProps .
  • Ao executar o connect(mapStateToProps, mapDispatchToProps) ele passará o método dispatch da store como parâmetro da função mapDispatchToProps .

Esta não é a melhor forma de se dispachar uma action, o mais correto seria utilizando os “actionCreators”

Agora o componente Counter

E agora?

Com o aplicativo Counter em mão, você está bem equipado para saber mais sobre o Redux.

“O que?! Tem mais?!”

Há muitas coisas que não abordamos aqui, com a esperança de tornar este guia facilmente digerível —action constants, actionCreators, middleware, thunks e chamadas assíncronas, seletores e assim por diante. Os documentos do Redux estão bem escritos e cobrem tudo isso e muito mais.

Mas você tem a idéia básica agora. Espero que você entenda como os dados fluem no Redux (dispatch (action) -> reducer -> new state -> re-render), e o que um reducer faz, e que action é, e como isso tudo se encaixa.

Mais uma vez não posso deixar de citar o artigo original escrito por: Dave Ceddia neste link https://daveceddia.com/how-does-redux-work/

Até mais.

--

--