Back

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

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:

  1. Você dispara uma ação de um componente
  2. A store do Redux passa a ação para o reducer
  3. O reducer cria um novo estado baseado na ação
  4. A store atualiza seu estado e notifica todos os componentes conectados
  5. 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:

  1. Selecione apenas os dados específicos que seu componente precisa
  2. Use seletores memorizados para dados derivados
  3. Use a função shallowEqual como segundo argumento para useSelector 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.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers