Entendendo React e Redux de uma vez por todas
Dois meses atrás, enquanto eu estava desenvolvendo um aplicativo em ReactJS, eu encontrei um problema que parecia bem simples, mas que me deu uma forte dor de cabeça: modificar um componente pai com uma ação do filho. Para explicar melhor, nós precisamos pensar no seguinte caso: você tem um header, e você quer mudar um texto dentro dele, baseado em uma função de um componente filho:
import React, { Component, Fragment } from 'react'
class Children extends Component {
render() {
return (
<Fragment>
<button>Mudar texto</button>
</Fragment>
)
}
}
export default class extends Component {
constructor() {
super()
this.state = { texto: 'Este texto' }
}
render() {
return (
<Fragment>
<h1>{this.state.texto}</h1>
<Children />
</Fragment>
)
}
}
Mesmo que não pareça, fazer com que uma ação button do Children mude o texto do componente principal é uma tarefa meio complicada. Redux é exatamente o que precisamos para lidar com um cenário destes.
Pensando em Redux
Teoricamente falando, o Redux seria um controlador de estados geral para sua aplicação. Compartilhar estados entre vários componentes diferentes se torna uma coisa muito fácil quando o utilizamos. O Redux é basicamente divido em 3 partes: store, reducers e actions.
A store
"store" é o nome dado pelo Facebook para o conjunto de estados da sua aplicação. Vamos pensar na store como um grande centro de informações, que possui disponibilidade para receber e entregar exatamente o que o seu componente requisita (seja uma função, ou uma informação propriamente dita). Tecnicamente, a store é um objeto JavaScript que possui todos os estados dos seus componentes.
Os reducers
Cada dado da store deve ter o seu próprio reducer, por exemplo: o dado "user" teria o seu reducer, chamado "userReducer". Um reducer é encarregado de lidar com todas as ações, como algum componente pedindo para alterar algum dado da store.
As actions
Actions são responsáveis por requisitar algo para um reducer. Elas devem ser sempre funções puras, o que, dizendo de uma forma leiga, quer dizer que elas devem APENAS enviar os dados ao reducer, nada além disso.
Instalando
Para usarmos o Redux, vamos precisar instalar o redux e o react-redux no nosso projeto. Execute isto no seu terminal:
npm install --save redux react-redux
Mãos na massa
Deixando a teoria de lado, vamos fazer algo simples: solucionar o problema do código no começo do artigo. Pra começar, devemos criar a nossa store, que ficará em um arquivo chamado store.js:
import { createStore } from 'redux'
import appReducer from './appReducer'
export default createStore(appReducer)
Eu aposto que foi bem mais simples do que você pensava, não foi!? Como eu só vou alterar um dado, minha aplicação vai ter apenas um Reducer, chamado "appReducer".
Agora, vamos criar o nosso appReducer.js:
const initialState = { text: 'Este texto' }
export default (state = initialState, action) => {
switch (action.type) {
case 'SET_TEXT':
return { ...state, text: action.payload }
default:
return state
}
}
Isso é um pouco mais complicado, mas eu vou explicar: toda vez que você executar uma action, ela vai ser reconhecida pelo seu nome (type), para que os reducers saibam diferi-las e estipular a função de cada uma. Toda ação é quase sempre enviada com algum dado (payload). No nosso caso, esse dado vai ser o novo texto que queremos definir. O initialState define os dados inicias dos nossos estados (não se preocupe se ver dados sendo definidos como null ou arrays vazios, isso acontece com bastante frequência). O return dos Reducers é quem diz o que vai ser passado para o estado da store (repare que o default retorna o próprio state, pois, caso o nome da ação não seja encontrado, ela provavelmente não se refere àquele reducer, então, não teremos nada para alterar).
Conectando com o React
Antes de começarmos, vamos ter de dizer ao React de qual store (sempre deve ser uma única store) iremos consumir os dados. Para fazermos isso, vamos no arquivo mais alto da nossa aplicação (costuma ser o index.js) para definirmos a store.
import ReactDOM from 'react-dom'
import React from 'react'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'))
O Provider é o responsável por dizer qual vai ser a nossa store, como podemos ver no código acima.
Criando uma ação
O ideal é que tenhamos um arquivo de ações para cada reducer. Vamos criar um arquivo chamado appActions.js:
export default {
setText(text) {
return { type: 'SET_TEXT', payload: text }
}
}
No nosso caso, temos apenas uma ação, que é atribuída à função setText(), que por sinal, é uma função pura (por conter apenas um nome e um dado). Quando essa função for executada (independente de onde), o nosso Reducer irá ouvir, e definir o novo texto que estamos enviando para o estado.
Executando a ação e obtendo os dados
A biblioteca react-redux possui uma função chamada connect. O connect, por sua vez, é responsável por estabelecer conexão entre algum componente e o Redux (store, reducers). Quando um componente está englobado pelo connect, ele recebe uma função chamada dispatch por props, para que você possa executar as suas ações (isso irá ficar mais claro no código). Para usarmos o connect, devemos dizer quais dados da store o nosso componente irá usar (estes dados também serão passados por props). No nosso caso, o único dado que vamos precisar será o "texto".
import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
import appActions from './appActions'
class Children extends Component {
setText() {
this.props.dispatch(appActions.setText('Aquele texto'))
}
render() {
return (
<Fragment>
<button onClick={() => this.setText()}>Mudar texto</button>
</Fragment>
)
}
}
const ChildrenConnected = connect(store => ({ text: store.text }))(Children)
class App extends Component {
render() {
return (
<Fragment>
<h1>{this.props.text}</h1>
<ChildrenConnected />
</Fragment>
)
}
}
export default connect(store => ({ text: store.text }))(App)
Por fim, repare que eu utilizo a função connect duas vezes: a primeira no Children, para possibilitar que ele despache uma ação; e a segunda no App, para permitir que ele acesse o valor da store.
Conclusão
Espero que tenha dado para entender um pouco sobre como o Redux funciona junto com o React. Para mim, o Redux se tornou uma peça fundamental em toda aplicação React que eu desenvolvo. Cheers!