Entendendo o Redux no React: Gerencie o Estado Como um Profissional

Está tendo dificuldades para gerenciar o estado entre vários componentes em sua aplicação React? À medida que seu aplicativo cresce, passar props através de várias camadas de componentes (prop drilling) torna-se complicado e propenso a erros. O Redux oferece uma solução fornecendo um sistema centralizado de gerenciamento de estado que torna suas aplicações React mais previsíveis e mais fáceis de depurar.
Neste guia abrangente, você aprenderá como o Redux funciona com o React, quando usá-lo e como implementá-lo efetivamente em seus projetos. Abordaremos tudo, desde conceitos fundamentais até a implementação prática com o Redux Toolkit, a maneira moderna de escrever código Redux.
Principais Aprendizados
- O Redux fornece gerenciamento de estado centralizado para aplicações React
- Use o Redux quando seu aplicativo tiver estado compartilhado complexo entre muitos componentes
- O Redux segue um fluxo de dados unidirecional: ações → reducers → store → UI
- O Redux Toolkit simplifica o desenvolvimento com Redux reduzindo código boilerplate
- A otimização de desempenho é crucial para aplicações Redux maiores
O que é Redux no React?
Redux é um contêiner de estado previsível para aplicações JavaScript, particularmente popular com React. Ele armazena todo o estado da sua aplicação em um único objeto imutável chamado ""store"". Esta abordagem centralizada para gerenciamento de estado resolve muitos problemas comuns em aplicações React:
- Prop drilling: Não é mais necessário passar o estado através de múltiplas camadas de componentes
- Sincronização de estado: Garante estado consistente entre componentes
- Atualizações previsíveis: Mudanças de estado seguem um fluxo de dados unidirecional estrito
- Depuração: Torna as mudanças de estado rastreáveis e previsíveis
O Redux não faz parte do React em si, mas funciona como uma biblioteca complementar através do React Redux, a biblioteca oficial de vinculação de UI do Redux para React.
// Basic Redux integration with React
import { Provider } from 'react-redux';
import store from './store';
function App() {
return (
<Provider store={store}>
<YourApplication />
</Provider>
);
}
Quando Você Deve Usar o Redux?
Nem toda aplicação React precisa do Redux. Considere usar o Redux quando:
- Seu aplicativo tem lógica de estado complexa compartilhada entre muitos componentes
- Componentes precisam acessar e atualizar o estado de diferentes partes da árvore de componentes
- Você precisa rastrear e depurar mudanças de estado em toda a sua aplicação
- Seu aplicativo tem uma base de código média a grande com vários desenvolvedores
Para aplicações menores ou componentes com estado localizado, o gerenciamento de estado integrado do React (useState, useReducer e Context API) geralmente é suficiente.
Conceitos Fundamentais do Redux
A Store: Fonte Única da Verdade
A store do Redux mantém toda a árvore de estado da sua aplicação. Diferentemente do estado de componente do React, que é distribuído entre componentes, o Redux centraliza todo o estado em um único lugar:
// Creating a Redux store
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './reducers';
const store = configureStore({
reducer: rootReducer
});
export default store;
Actions: Descrevendo Mudanças de Estado
Actions são objetos JavaScript simples que descrevem o que aconteceu em sua aplicação. Elas são a única maneira de enviar dados para a store do Redux:
// An action object
{
type: 'counter/incremented',
payload: 1
}
// Action creator function
const increment = (amount) => {
return {
type: 'counter/incremented',
payload: amount
}
}
Cada action deve ter uma propriedade type
que descreve que tipo de ação ela é. A propriedade payload
contém quaisquer dados necessários para a ação.
Reducers: Funções Puras para Atualizações de Estado
Reducers são funções puras que recebem o estado atual e uma ação, e então retornam um novo estado. Eles especificam como o estado da aplicação muda em resposta às ações:
// A simple reducer function
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'counter/incremented':
return state + action.payload;
case 'counter/decremented':
return state - action.payload;
default:
return state;
}
}
Os reducers devem:
- Ser funções puras (sem efeitos colaterais)
- Nunca mutar o estado diretamente
- Retornar um novo objeto de estado quando ocorrerem mudanças
Fluxo de Dados Unidirecional
O Redux segue um fluxo de dados unidirecional estrito:
- Você dispara uma ação de um componente
- A store do Redux passa a ação para o reducer
- O reducer cria um novo estado baseado na ação
- A store atualiza seu estado e notifica todos os componentes conectados
- Os componentes são renderizados novamente com o novo estado
Este fluxo unidirecional torna as mudanças de estado previsíveis e mais fáceis de entender.
Integração do React Redux
Provider: Conectando o Redux ao React
O componente Provider
disponibiliza a store do Redux para todos os componentes em sua aplicação:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Hooks: Acessando o Redux em Componentes
O React Redux fornece hooks que permitem que seus componentes interajam com a store do Redux:
useSelector: Lendo o Estado
import { useSelector } from 'react-redux';
function CounterDisplay() {
// Select the counter value from the store
const count = useSelector(state => state.counter.value);
return <div>Current count: {count}</div>;
}
O hook useSelector
:
- Recebe uma função seletora que extrai dados do estado da store
- Re-renderiza o componente quando o estado selecionado muda
- Otimiza o desempenho evitando re-renderizações desnecessárias
useDispatch: Atualizando o Estado
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<div>Count: {count}</div>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
O hook useDispatch
:
- Retorna a função dispatch da store
- Permite que você dispare ações de qualquer componente
Redux Moderno com Redux Toolkit
Redux Toolkit é a maneira oficial e recomendada de escrever lógica Redux. Ele simplifica o desenvolvimento Redux:
- Reduzindo código boilerplate
- Incluindo utilitários úteis para padrões comuns
- Fornecendo bons padrões para configuração da store
- Permitindo o uso de mutações diretas de estado em reducers (via Immer)
Criando uma Store com configureStore
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
import todosReducer from './features/todos/todosSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
todos: todosReducer
}
});
export default store;
Definindo Lógica de Estado com createSlice
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write ""mutating"" logic in reducers
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
Lidando com Operações Assíncronas com createAsyncThunk
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// Create an async thunk for fetching data
export const fetchUserData = createAsyncThunk(
'users/fetchUserData',
async (userId, thunkAPI) => {
const response = await fetch(`https://api.example.com/users/${userId}`);
return await response.json();
}
);
const userSlice = createSlice({
name: 'user',
initialState: {
data: null,
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserData.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchUserData.fulfilled, (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
})
.addCase(fetchUserData.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
}
});
export default userSlice.reducer;
Exemplo de Implementação Prática
Vamos construir uma aplicação de contador simples com o Redux Toolkit:
1. Configurar a store
// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer
}
});
2. Criar um slice para o recurso de contador
// features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// Selector
export const selectCount = (state) => state.counter.value;
3. Conectar a store ao React
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
4. Usar o Redux em um componente
// features/counter/Counter.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
increment,
decrement,
incrementByAmount,
selectCount
} from './counterSlice';
export function Counter() {
const count = useSelector(selectCount);
const dispatch = useDispatch();
const [incrementAmount, setIncrementAmount] = useState('2');
return (
<div>
<div>
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
</div>
<div>
<input
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
onClick={() =>
dispatch(incrementByAmount(Number(incrementAmount) || 0))
}
>
Add Amount
</button>
</div>
</div>
);
}
Técnicas de Otimização de Desempenho
Seletores Memorizados com Reselect
Reselect (incluído no Redux Toolkit) permite criar funções seletoras memorizadas que só recalculam resultados quando as entradas mudam:
import { createSelector } from '@reduxjs/toolkit';
// Basic selectors
const selectItems = state => state.items;
const selectFilter = state => state.filter;
// Memoized selector
export const selectFilteredItems = createSelector(
[selectItems, selectFilter],
(items, filter) => {
// This calculation only runs when items or filter changes
return items.filter(item => item.includes(filter));
}
);
Evitando Renderizações Desnecessárias
Para evitar que componentes sejam renderizados novamente quando partes não relacionadas do estado mudam:
- Selecione apenas os dados específicos que seu componente precisa
- Use seletores memorizados para dados derivados
- Use a função
shallowEqual
como segundo argumento parauseSelector
ao selecionar objetos
import { useSelector, shallowEqual } from 'react-redux';
// This component will only re-render when user.name or user.email changes
function UserInfo() {
const { name, email } = useSelector(state => ({
name: state.user.name,
email: state.user.email
}), shallowEqual);
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
Redux vs. Outras Soluções de Gerenciamento de Estado
Redux vs. Context API
Context API:
- Integrado ao React
- Mais simples para aplicações pequenas a médias
- Não requer bibliotecas adicionais
- Menos código boilerplate
- Ferramentas de depuração limitadas
Redux:
- Mais poderoso para lógica de estado complexa
- Melhor desempenho para atualizações frequentes
- Excelente depuração com Redux DevTools
- Suporte a middleware para efeitos colaterais
- Abordagem mais estruturada para gerenciamento de estado
Redux vs. Zustand/Recoil/MobX
Considere essas alternativas quando:
- Você precisa de uma API mais simples com menos código boilerplate (Zustand)
- Você quer gerenciamento de estado baseado em átomos (Recoil)
- Você prefere uma abordagem mais orientada a objetos e reativa (MobX)
O Redux ainda é a melhor escolha quando você precisa:
- Uma solução madura e testada em batalha
- Excelentes capacidades de depuração
- Um grande ecossistema de middleware e extensões
- Gerenciamento de estado previsível para aplicações complexas
Conclusão
O Redux fornece uma solução de gerenciamento de estado poderosa e previsível para aplicações React. Embora introduza alguma complexidade e código boilerplate, o Redux Toolkit simplifica significativamente a experiência de desenvolvimento. Para aplicações complexas com estado compartilhado entre muitos componentes, o Redux oferece benefícios claros em termos de manutenção, depuração e previsibilidade.
Considere cuidadosamente as necessidades da sua aplicação ao escolher uma solução de gerenciamento de estado. Para aplicações mais simples, o gerenciamento de estado integrado do React pode ser suficiente, mas à medida que sua aplicação cresce em complexidade, a abordagem estruturada do Redux torna-se cada vez mais valiosa.
Perguntas Frequentes
Use o Redux para aplicações complexas com atualizações frequentes e estado compartilhado. Use o Context para aplicações mais simples ou quando você precisa compartilhar valores que não mudam com frequência.
O Redux tem conceitos fundamentais que podem levar tempo para entender, mas o Redux Toolkit simplifica significativamente a curva de aprendizado. Comece com exemplos pequenos e aumente gradualmente a complexidade.
Não necessariamente. Embora o Redux adicione alguma sobrecarga, ele inclui otimizações de desempenho e pode realmente melhorar o desempenho evitando re-renderizações desnecessárias.
Use middleware como Redux Thunk (incluído no Redux Toolkit) ou Redux Saga para fluxos assíncronos mais complexos. O createAsyncThunk do Redux Toolkit torna as operações assíncronas diretas.
Sim. Embora hooks como useReducer e useContext possam lidar com necessidades de gerenciamento de estado mais simples, o Redux ainda se destaca no gerenciamento de estado complexo de aplicações, especialmente em grandes equipes.
Use a Extensão Redux DevTools para inspecionar o estado, ações e mudanças de estado ao longo do tempo. É um dos recursos mais poderosos do Redux.