Introdução Funcional à React-Redux
Rodrigo Queiroz
Posted on March 17, 2021
Disclaimer
- Esse é um artigo introdutório e apresenta uma forma de utilização, com hooks. O objetivo desse artigo é mostrar como usar, se a sua pergunta é quando olhe as referências.
- As dependências desse artigo são Javascript vanilla, React e noção de estado.
- As recomendações que escrevo são para uma aplicação padrão, existem casos que fogem a regra.
Introdução
Ok, vamos por partes: Redux é uma biblioteca JavaScript de gerenciamento de estados. A partir dela foi construída a biblioteca React Redux, que implementa facilidades como hooks.
Mas por que usa-la? 1- torna a sua aplicação mais fácil de ser testada e 2- poder separar a lógica de gerenciamento de estados da UI 3- elimina a necessidade de "props drilling", ou seja, passar props para uma sequência de filhos, para que filhos em ramos diferentes da árvore de componentes possam usa-las.
Você pode encontrar um exemplo de aplicação que aplica props drilling aqui (movie tem que ser definido em Home) e uma alternativa dos mesmo components com redux aqui (Home não precisa saber do estado movie).
Como funciona?
O mínimo necessário para um redux é:
-
1- Store
A store é o estado global da aplicação, deve haver apenas uma por aplicação e ela nunca deve ser alterada diretamente, para alterações utilize os reducers. Para criar uma store utilize
createStore()
passando como argumento o reducer. Para a store estar disponível à toda a aplicação envolva-a com o componente Provider de 'react-redux',<Provider store={store}>
. Esse Provider age como um<Context.Provider>
(por baixo dos panos é!!), mas com a conveniência de não ter que passar o contexto nos elementos filhos. -
2- Reducer
O reducer é quem alterar a store. Ele é uma função Javascript que recebe dois argumentos e retorna um estado (geralmente um objeto). É possível criar diversos reducers e combina-los através de
combineReducers()
, passando-os como argumento dentro de um objeto. As chaves desse objeto serão utilizadas para nomear o estado de cada reducer. As alterações devem ser feitas de maneira imutável.
// /src/store/user.js export const userReducer = (state = { name: '' }, action) => { switch(action.type) { /* Nomear os tipos com o padrão domínio/ação é indicado pelo guia de estilos do redux */ case: 'user/setUser': return { name: action.payload }; case: 'user/deleteUser': return { name: '' }; default: return { ..state }; }; }; // /src/App.js import { createStore, combineReducers } from 'redux'; import { Provider } from 'react-redux'; import { formReducer } from './store/form.js'; import { userReducer } from './store/user.js'; import Home from './pages/Home'; const App = () => { const store = createStore(combineReducers({ form: formReducer, user: userReducer, })); return ( <Provider store={store}> <Home /> {/* outros componentes */} </Provider> ); };
Ok, agora temos uma store e ela está disponível para toda a aplicação. Mas como podemos usar os valores contidos nela? Como, de fato, mudo o estado? Bom, pra isso você vai precisar de mais três coisas:
-
3- Selector
O papel do selector é expor o estado armazenado na store para a sua aplicação. Caso tenha utilizado apenas um reducer é possível faze-lo com
store.getState()
, sendostore
o objeto retornado por createStore(). Caso tenha utilizadocombineReducers()
, opte por useSelector de 'react-redux'. Esse função espera uma função de callback que será chamada com o estado global e um parametro opcional (bateu a curiosidade? olha as referências).
// /src/pages/Home/index.js import { useSelector } from 'react-redux'; export default const Home = () => { const user = useSelector((state) => state.user); return ( <header> <p>{`Olá, ${user.name}`}</p> {/* resto da página */} </header> ); };
-
4- Dispatch
O dispatch é a função que chamará o reducer. Ele é o retorno de
useDispatch()
de 'react-redux' -
5- Action
São as instruções que o dispatch enviará para o reducer. Ela é um objeto que contem, tradicionalmente, as chaves type e payload. Type informará que tipo de ação o reducer deverá fazer e payload conterá as informações relevantes para essa ação. Abaixo um exemplo de utilização:
// /src/pages/Home/index.js import { useSelector, useDispatch } from 'react-redux'; const Home = () => { //repare que a chave declarada em combineReducers é usada aqui const user = useSelector((state) => state.user); const dispatch = useDispatch(); const handleClick = () => { dispatch({type: 'user/setUser', payload: 'Sergio'}) } return ( <header> <p>{`Olá, ${user.name}`}</p> <button onClick={handleClick}>Meu nome é Sergio</button> </header> ); };
Uma boa prática é utilizar um action creator, que é uma função que retorna um objeto action. Pode parecer desnecessário, mas deixa o código mais legível e torna a manutenção mais simples.
// /src/store/user.actions.js export function doSetUser(payload) { return { type: 'user/setUser', payload }; }; export function doDeleteUser() { return { type: 'user/deleteUser' }; }; // /src/pages/Home/index.js import { useSelector, useDispatch } from 'react-redux'; import { doSetUser } from '../../store/user.actions.js'; const Home = () => { //repare que a chave usada em combineReducers é usada aqui const user = useSelector((state) => state.user); const dispatch = useDispatch(); const handleClick = () => { // código mais limpo dispatch(doSetUser('Sergio')) }; return ( <header> <p>{`Olá, ${user.name}`}</p> <button onClick={handleClick}>Meu nome é Sergio</button> </header> ); };
Recapitulando
A store é quem guarda o estado e é criada a partir de createStore(reducer)
. Para modificar um estado, chama-se o dispatch (do useDispatch()
) com uma action (objeto com a forma {type, payload}
) e o estado será modificado segundo a lógica definida no reducer (função que retorna um estado). Para ter acesso ao estado utiliza-se um selector (podendo ser useSelector(callback)
ou store.getState()
).
Curiosidade
Redux foi nomeado assim porque age como um Array.prototipe.reduce(), no sentido que, ambos, a cada iteração, recebem dois parâmetros (estado e ação, no caso do redux) e retornam um novo estado. No reduce itera-se por um array e no redux itera-se pelo tempo de vida da aplicação.
Reduce
const incrementOrDecrement= [true, false, true, true]
const total = incrementOrDecrement.reduce((accumulator, current) => {
if (current) {
return accumulator + 1;
} else {
return accumulator - 1;
}
}, 0);
O reduce vai iterar por cada elemento do array incrementOrDecrement, sendo que a cada iteração o elemento será o current
e o que foi retornado pelo iteração anterior, o accumulator
.
Então total = 2
.
Redux
Analogamente, poderiamos ter o seguinte reducer:
const reducer = (state, action) => {
switch(action.type) {
case 'increment':
return state + 1
case 'decrement':
return state - 1
}
};
E durante o ciclo de vida da aplicação, um usuário clicar em botões que disparem a seguinte sequência de dispatchs:
dispatch({ type: 'increment' })
dispatch({ type: 'decrement' })
dispatch({ type: 'increment' })
dispatch({ type: 'increment' })
E de novo chegaríamos a store.getState() = 2
Sob essa ótica, o conjunto de todas as interações do usuário com a sua aplicação poderia ser entendido como um grande array de actions, que é reduzido pelo Redux ao estado final pós interação com usuário.
Referências
Quer saber mais sobre o assunto?
- React Redux Provider
- Combinando Reducers
- Guia de estilo do Redux
- Mudanças de forma imutável
- Parâmetro opcional para useSelector
- Quando usar Redux
Gostaria de agradecer a todos que leram e especialmente a esse povo pelas sugestões:
E aí, ficou com alguma duvida? Discorda de algo? Comenta aí embaixo, bora conversar!
Posted on March 17, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.