12k
All articles

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

O Redux centraliza dados em uma store para simplificar o estado no React. Actions, reducers e Redux Toolkit trabalham juntos para atualizações previsíveis.

OpenReplay Team
OpenReplay Team
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

Quando devo usar Redux vs. React Context?

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 é difícil de aprender?

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.

O Redux torna meu aplicativo mais lento?

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.

Como lidar com operações assíncronas no Redux?

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.

O Redux ainda é relevante com os hooks do React?

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.

Como depurar aplicações Redux?

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

We use cookies to improve your experience. By using our site, you accept cookies.