Iniciando com Redux em 9 passos
Este tutorial será o mais curto e objetivo sobre Redux que você vai encontrar!
Boa tarde pessoal! Tudo bem?
Com este artigo pretendo engajar aqueles que estão iniciando com Redux, focando apenas nas partes básicas, usando o mínimo de implementação para que funcione. Recomendo um conhecimento básico de React, JSX, ES6+, e também alguns termos de programação funcional.
Este artigo não contempla:
- As razões para se usar Redux. Caso você queira saber mais, deixo-lhes aqui um artigo: https://medium.com/trainingcenter/tira-redux-coloca-redux-eb3c4f1d7db8
- Redux Middlewares, provavelmente em um artigo futuro.
- Testes, talvez em um artigo futuro também. Mas se estiver curioso, aqui tem um artigo da própria documentação: http://redux.js.org/docs/recipes/WritingTests.html
No final do tutorial deixei o link do repositório com o exemplo funcional. Dividi o tutorial em 9 passos.
Primeiramente explicarei brevemente a arquitetura do Redux
Arquitetura do Redux
O Redux é uma implementação criada pelo Dan Abramov da arquitetura Flux, que foi criada pelo Facebook. Ela soluciona o problema de compartilhamento de estados entre componentes, tornando-o unidirecional. A ilustração a seguir descreve tudo:
Através da ilustração percebemos que o Redux simplesmente simplifica a evolução de estados de uma aplicação quando há múltiplos estados para controlar e muitos componentes que precisam atualizar ou se inscrever nessa evolução, tirando a responsabilidade de cada componente de guardar o estado e passando para uma centralizada e única Store.
Fluxo de uma evolução de estado
Como uma imagem vale mais do que mil palavras, segue abaixo um diagrama que mostra como funciona o fluxo de uma evolução de estado com Redux, com a explicação de cada parte a seguir.
Para realizar tal fluxo, o Redux depende de 4 partes:
- Store: é o container que armazena e centraliza o estado geral da aplicação. Ela é imutável, ou seja, nunca se altera, apenas evolui.
Podemos ter apenas uma Store por aplicação, ou seja, ela é a Única Fonte de Verdade (Single Source of Truth).
- Actions: são fontes de informações que são enviadas da aplicação para a Store. São disparadas pelas Action Creators, que são simples funções que, ao serem executadas, ativam os Reducers.
- Reducers: recebem e tratam as informações para que sejam ou não enviadas à Store.
- Conexão dos componentes ao Redux: para poderem se inscrever à evolução de estados da Store ou disparar eventos para evoluí-la.
Pode não ter ficado muito claro agora, mas não precisa se preocupar, pois com a aplicação será mais fácil de entender. Caso queira informações mais detalhadas, recomendo a leitura da documentação oficial:
1. Instale e rode o Create React App
Create React App é um boilerplate criado pelo Facebook para iniciar rapidamente um projeto com React. Ele já vem com algumas dependências e plugins instalados, além de algumas pré-configurações.
Caso você possua npm 5.2+ ou maior, poderá instalar desta forma:
npx create-react-app create-react-redux-app
Se não tiver, é necessário instalar o Create React App globalmente. Rode em seu terminal o comando:
npm i -g create-react-app
… e ao finalizar:
create-react-app create-react-redux-app
O create-react-redux-app é o nome da pasta que será criada. Agora entre na pasta:
cd create-react-redux-app
… e rode com Yarn:
yarn start
ou com npm:
npm start
Uma janela no navegador abrirá, na porta http://localhost:3000/ exibindo:
2. Adapte a View para o tutorial
Acesse o arquivo create-react-redux-app/src/App.js e substitua a marcação (negritei as mudanças):
import React, { Component } from 'react';
import './App.css';class App extends Component {
render() {
return (
<div className="App" style={{ paddingTop: '10px' }}>
<input type='text' />
<button>
Click me!
</button>
<h1>teste</h1>
</div>
);
}
}export default App;
Ficará parecido com isto:
A idéia é que, quando clicarmos no botão Click me!, o texto "teste" seja substituído pelo conteúdo que está no input, via Redux.
3. Instale o Redux e o React Redux
O Redux é a lib de container de estados, enquanto o React Redux realiza a conexão entre React e o Redux.
No terminal no diretório do exemplo onde está o package.json, rode:
Para Yarn:
yarn add --dev redux react-redux
Para npm:
npm i -D redux react-redux
4. Crie a Store
Crie uma pasta na raíz da pasta /src chamada store. Dentro dela, crie um arquivo index.js com o conteúdo:
import { createStore } from 'redux';
import { Reducers } from '../reducers';export const Store = createStore(Reducers);
Aqui criamos a Store e dizemos quais são os seus respectivos Reducers. Vamos criá-los adiante.
5. Crie o Reducer
Como a Store é a Única Fonte de Verdade, teremos que combinar todos os reducers da aplicação e enviar à Store. Crie uma pasta na raíz da pasta /src chamada reducers. Dentro dela, crie um arquivo index.js com o conteúdo:
import { clickReducer } from './clickReducer';
import { OtherReducer } from './otherReducer';
import { combineReducers } from 'redux';export const Reducers = combineReducers({
clickState: clickReducer,
otherState: otherReducer
});
Caso sua aplicação possua vários reducers, você pode combiná-los para enviá-los à Store da seguinte forma, usando o método combineReducers
.
A chave do objeto é o nome na qual o estado será acessado pela aplicação, enquanto o seu valor é o Reducer, função pura que filtra os dados e que criaremos adiante. Ficará de sua escolha caso prefira usar o mesmo nome para a chave e valor.
Aqui definimos qual será a chave do Reducer na Store quando quisermos acessar o seu estado, além de podermos combinar vários Reducers para serem conectados à Store.
Crie o arquivo clickReducer.js na mesma pasta com o seguinte conteúdo:
const initialState = {
newValue: ''
};export const clickReducer = (state = initialState, action) => {
switch (action.type) {
case 'CLICK_UPDATE_VALUE':
return {
...state,
newValue: action.newValue
};
default:
return state;
}
};
Este será o nosso Reducer que será acionado pela Action caso o type dela seja CLICK_UPDATE_VALUE
, caso contrário manterá o estado atual.
IMPORTANTE: esta função deverá ser pura, ou seja, retornar um novo objeto, pois lembrando, a Store é imutável. Conseguimos preservar o restante do estado usando o
...state
, que recupera o estado anterior e passa para o novo objeto.
O código abaixo está sujeito a bugs:
export const clickReducer = (state = initialState, action) => {
switch (action.type) {
case 'CLICK_UPDATE_VALUE':
return state.newValue = action.newValue;
default:
return state;
}
}
Agora vamos criar a Action.
6. Crie a Action e sua Action Creator
Crie uma pasta na raíz da pasta /src chamada actions. Dentro dela, crie um arquivo index.js com o conteúdo:
export const clickButton = value => ({
type: 'CLICK_UPDATE_VALUE',
newValue: value
});
Aqui a função é a Action Creator, e o que ela retorna, que é o objeto, é a Action. Ao ser disparada, ela comunicará ao Reducer que o type é CLICK_UPDATE_VALUE
, além do valor newValue: value
que deverá ser atualizado na Store. Novamente aqui, caso prefira usar o mesmo nome para chave e valor, ficará de sua escolha.
Os types, que são as Action Types, recomendo separar num arquivo próprio para importarmos apenas as constantes, desta forma, num arquivo:
src/actions/actionTypes.js:
export const CLICK_UPDATE_VALUE = 'CLICK_UPDATE_VALUE';
src/reducers/clickReducer.js atualizado:
import { CLICK_UPDATE_VALUE } from '../actions/actionTypes';const initialState = {
newValue: ''
};export const clickReducer = (state = initialState, action) => {
switch (action.type) {
case CLICK_UPDATE_VALUE:
return {
...state,
newValue: action.newValue
};
default:
return state;
}
};
src/actions/index.js atualizado:
import { CLICK_UPDATE_VALUE } from './actionTypes';export const clickButton = value => ({
type: CLICK_UPDATE_VALUE,
newValue: value
});
Chegou o esperado momento de fazermos a conexão com os componentes!
7. Crie o Provider para conectar a aplicação à Store
No arquivo src/index.js faça algumas alterações, ele deverá ficar assim (negritei as mudanças):
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from 'react-redux';
import { Store } from './store';ReactDOM.render(
<Provider store={Store}>
<App />
</Provider>
, document.getElementById('root'));registerServiceWorker();
Aqui é onde o estado da Store se conectará com toda a aplicação, através do Provider Pattern, que possibilita que a Store seja acessível a todos os componentes abaixo dele. Veja mais sobre em:
8. Conecte o estado da Store ao componente
Para fazer a conexão com a Store, agora devemos atualizar o nosso componente src/App.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import './App.css';class App extends Component {
render() {
const { newValue } = this.props; return (
<div className="App" style={{ paddingTop: '10px' }}>
<input type='text' />
<button>
Click me!
</button>
<h1>{newValue}</h1>
</div>
);
}
}const mapStateToProps = store => ({
newValue: store.clickState.newValue
});export default connect(mapStateToProps)(App);
O método mapStateToProps
transforma um trecho do estado da Store em uma prop utilizável pelo componente, com o nome newValue
.
Ao acessarmos a chave clickState
, que foi definida no arquivo src/reducers/index.js na linha 5:
clickState: clickReducer
… podemos extrair a sua chave newValue
, que foi definida no arquivo src/reducers/clickReducer.js na linha 12:
newValue: action.newValue
No momento de exportar o componente, precisamos fazer a sua conexão através do método connect
do React Redux, passando o método mapStateToProps
como parâmetro e o componente atual como parâmetro na função retornada. Este padrão no React se chama High Order Component.
Ao fazermos esta conexão, podemos usar o newValue
como prop no componente:
...
const { newValue } = this.props;
......
<h1>{newValue}</h1>
...
Para sabermos se está funcionando, basta trocarmos o initialState
no arquivo src/reducers/clickReducer.js na linha 4 e conferirmos se reflete na página:
const initialState = {
newValue: 'Atualizado via Redux!'
};
9. Disparando um evento para evoluir a Store
Agora vamos criar a funcionalidade de evoluir o estado da Store a partir do preenchimento do input e o clique no botão Click me!. O código deverá novamente ser atualizado conforme a seguir:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { clickButton } from './actions';
import './App.css';class App extends Component {
state = {
inputValue: ''
} inputChange = event => {
this.setState({
inputValue: event.target.value
})
} render() {
const {
clickButton,
newValue
} = this.props; const { inputValue } = this.state; return (
<div className="App" style={{ paddingTop: '10px' }}>
<input
onChange={this.inputChange}
type='text'
value={inputValue}
/>
<button onClick={() => clickButton(inputValue)}>
Click me!
</button>
<h1>{newValue}</h1>
</div>
);
}
}const mapStateToProps = store => ({
newValue: store.clickState.newValue
});const mapDispatchToProps = dispatch =>
bindActionCreators({ clickButton }, dispatch);export default connect(mapStateToProps, mapDispatchToProps)(App);
Criamos o método mapDispatchToProps
para converter a Action Creator clickButton
que criamos no arquivo src/actions/index.js em uma prop para o componente. O método bindActionCreators
facilita este trabalho. Ao clicar no botão Click me!, o valor do state inputValue
que foi alterado pelo input text é enviado à Store pelo método clickButton
, que foi mapeado no componente como prop.
Por fim, passamos o método mapDispatchToProps
como segundo parâmetro do método connect
. Agora é só testar!
É importante lembrar que há diversas formas de se disparar eventos à Store, eu abordarei algumas delas em um futuro artigo.
Conclusão
Por enquanto isto é tudo pessoal. Segue o link do repositório com o exemplo:
Caso eu tenha esquecido de algo importante, ou tenha ficado confuso, ou a melhorar, ou vocês tenham dúvidas, algo a acrescentar, por favor deixem nos comentários! Lembrando que não contemplei os Redux Middlewares, que provavelmente ficará para um próximo artigo!
Espero que tenham gostado! E muito obrigado por terem acompanhado!