TypeScript e Redux: Como Tipar Ações e Estados de Forma Eficiente

18/09/2024

Por Que Usar TypeScript com Redux?

Redux é uma das bibliotecas mais populares para gerenciamento de estado em aplicações JavaScript. No entanto, quando os estados e ações se tornam complexos, o uso de Redux pode introduzir erros difíceis de rastrear. Ao combinar Redux com TypeScript, você pode garantir a tipagem estática de ações, estados e dispatchers, o que melhora a segurança do código e torna a refatoração mais fácil, além de fornecer feedback antecipado durante o desenvolvimento.

Configurando TypeScript com Redux

Antes de tipar as ações e estados, precisamos configurar um ambiente de desenvolvimento com React, Redux e TypeScript. Siga as etapas a seguir para configurar o projeto.

1. Instalando Dependências

Primeiro, crie um projeto React com TypeScript e instale as dependências do Redux:

npx create-react-app meu-projeto --template typescript
cd meu-projeto
npm install redux react-redux @types/react-redux

Isso instala o Redux, o React-Redux para conectar o Redux ao React, e as definições de tipos para o TypeScript.

2. Estrutura do Projeto

Vamos organizar nosso projeto com as seguintes pastas e arquivos:

src/
  actions/
    contadorActions.ts
  reducers/
    contadorReducer.ts
  store/
    index.ts
  components/
    Contador.tsx
  App.tsx

Com essa estrutura, separaremos as ações, reducers e componentes para organizar o código de forma modular e escalável.

Tipando o Estado com TypeScript

O primeiro passo para tipar o Redux com TypeScript é garantir que o estado global e o estado individual dos reducers estejam corretamente definidos. Vamos começar criando um estado para um contador.

1. Definindo a Interface do Estado

Crie um arquivo chamado contadorReducer.ts dentro da pasta reducers com a seguinte interface para o estado:

export interface ContadorState {
  valor: number;
}

const estadoInicial: ContadorState = {
  valor: 0
};

A interface ContadorState define a estrutura do estado, que neste caso contém apenas uma propriedade valor do tipo number.

Tipando as Ações do Redux

As ações no Redux descrevem eventos que ocorrem na aplicação e são despachadas para modificar o estado. Com TypeScript, podemos garantir que as ações sejam tipadas corretamente e que os reducers recebam sempre as ações esperadas.

1. Definindo Tipos de Ação

Vamos criar um arquivo chamado contadorActions.ts na pasta actions para definir os tipos e criadores de ação:

export enum ContadorActionTypes {
  INCREMENTAR = 'INCREMENTAR',
  DECREMENTAR = 'DECREMENTAR'
}

interface IncrementarAction {
  type: ContadorActionTypes.INCREMENTAR;
}

interface DecrementarAction {
  type: ContadorActionTypes.DECREMENTAR;
}

export type ContadorActions = IncrementarAction | DecrementarAction;

export const incrementar = (): IncrementarAction => ({
  type: ContadorActionTypes.INCREMENTAR
});

export const decrementar = (): DecrementarAction => ({
  type: ContadorActionTypes.DECREMENTAR
});

Aqui, usamos o enum ContadorActionTypes para definir os tipos de ação. Criamos interfaces para cada ação e, em seguida, exportamos uma união de todas as ações disponíveis como ContadorActions. Isso permite que o TypeScript verifique se estamos passando ações válidas para o reducer.

Tipando o Reducer no Redux

O reducer é a função responsável por atualizar o estado com base nas ações despachadas. Vamos criar e tipar o reducer para o contador.

1. Criando o Reducer

Agora, no arquivo contadorReducer.ts, adicione o código para o reducer:

import { ContadorState } from './contadorReducer';
import { ContadorActionTypes, ContadorActions } from '../actions/contadorActions';

const estadoInicial: ContadorState = {
  valor: 0
};

export const contadorReducer = (state: ContadorState = estadoInicial, action: ContadorActions): ContadorState => {
  switch (action.type) {
    case ContadorActionTypes.INCREMENTAR:
      return { ...state, valor: state.valor + 1 };
    case ContadorActionTypes.DECREMENTAR:
      return { ...state, valor: state.valor - 1 };
    default:
      return state;
  }
};

O reducer é tipado com ContadorState e ContadorActions, garantindo que o estado e as ações sigam as estruturas definidas. O TypeScript verifica se as ações são válidas e se o estado está sendo modificado corretamente.

Configurando a Store

A store é o centro do Redux, onde o estado é mantido. Vamos configurar a store com o reducer do contador.

1. Criando a Store

Em store/index.ts, adicione o seguinte código para configurar a store:

import { createStore } from 'redux';
import { contadorReducer } from '../reducers/contadorReducer';

export const store = createStore(contadorReducer);

Essa configuração cria a store Redux usando o reducer do contador. Agora, a store está pronta para ser conectada ao React.

Conectando Redux ao React

Agora que configuramos o Redux, vamos conectar a store ao React. Para isso, usaremos o useSelector e useDispatch do React-Redux, garantindo que as funções e estados sejam corretamente tipados.

1. Criando o Componente Contador.tsx

Crie um componente de contador em components/Contador.tsx:

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { incrementar, decrementar } from '../actions/contadorActions';
import { ContadorState } from '../reducers/contadorReducer';

const Contador: React.FC = () => {
  const valor = useSelector((state: ContadorState) => state.valor);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Contador: {valor}</h1>
      <button onClick={() => dispatch(incrementar())}>Incrementar</button>
      <button onClick={() => dispatch(decrementar())}>Decrementar</button>
    </div>
  );
};

export default Contador;

O useSelector é tipado para garantir que o estado retornado seja do tipo ContadorState. O useDispatch é usado para despachar as ações incrementar e decrementar.

Conectando o Componente ao App

No arquivo App.tsx, conecte o componente Contador à store Redux:

import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import Contador from './components/Contador';

const App: React.FC = () => {
  return (
    <Provider store={store}>
      <Contador />
    </Provider>
  );
};

export default App;

O Provider é usado para disponibilizar a store Redux ao componente Contador. Agora, sua aplicação está conectada ao Redux com TypeScript garantindo a segurança de tipos.

Boas Práticas ao Usar Redux com TypeScript

  • Use enum para Ações: Defina suas ações como enum para garantir que os tipos de ação sejam consistentes e fáceis de manter.
  • Tipagem Rigorosa para Estados e Ações: Sempre defina interfaces claras para o estado e para as ações. Isso ajuda a prevenir erros e torna o código mais legível.
  • Use useSelector e useDispatch com Tipagem: Garanta que as funções useSelector e useDispatch estejam corretamente tipadas, melhorando a segurança e previsibilidade do código.
  • Separe Ações e Reducers: Organize as ações e reducers em arquivos separados para melhorar a manutenibilidade e escalabilidade do projeto.

Conclusão

Combinando Redux com TypeScript, você pode criar aplicações com gerenciamento de estado fortemente tipado e previsível. Tipar ações, estados e reducers garante que o fluxo de dados seja seguro e consistente em toda a aplicação, tornando o processo de desenvolvimento mais eficiente e menos propenso a erros. Seguindo boas práticas, como a tipagem rigorosa e a organização modular do código, seu projeto se tornará mais escalável e fácil de manter.