Frontend & Mobile Nathan Geeksman

Gerenciamento de Estado: Redux, Context API ou Zustand?

Gerenciamento de Estado: Redux, Context API ou Zustand?

Gerenciamento de Estado: Redux, Context API ou Zustand?

Introdução

O gerenciamento de estado é um tópico crucial no desenvolvimento de aplicativos front-end complexos, tornando-se cada vez mais importante a escolha do método correto para lidar com essa complexidade.

Com a evolução das tecnologias e frameworks, surgem novas abordagens para gerenciar o estado dos componentes, levantando questões sobre quais são as melhores práticas e soluções para cada tipo de aplicativo. Nesse contexto, é comum se perguntar: Redux, Context API ou Zustand? Qual é a melhor escolha?

Neste artigo, vamos explorar essas três opções de gerenciamento de estado, abordando suas principais características e casos de uso. Além disso, forneceremos uma visão geral sobre como cada uma delas pode ser aplicada em diferentes cenários.

O objetivo principal deste texto é ajudar os desenvolvedores a escolher o melhor framework para atender às necessidades específicas do seu projeto, garantindo que as decisões técnicas sejam informadas e baseadas em conhecimentos sólidos.

O que é e por que importa

O gerenciamento de estado (state management) é a prática de controlar como os componentes de uma aplicação front-end acessam, atualizam e compartilham dados entre si. Isso envolve lidar com escopos de visibilidade, garantir a consistência dos estados entre diferentes partes da aplicação e abordar questões relacionadas à persistência do estado.

Em aplicações front-end complexas, o gerenciamento de estado é crucial por várias razões. Em primeiro lugar, ele ajuda a evitar a propagação de mudanças inesperadas nos componentes da aplicação, que podem levar a bugs difíceis de solucionar. Além disso, ao centralizar o gerenciamento do estado, é possível reutilizar código e melhorar a manutenibilidade do projeto.

Outro aspecto importante é que um gerenciamento de estado eficaz pode ajudar a criar uma aplicação mais escalável e flexível, capaz de lidar com diferentes cenários e requisitos sem necessidade de reestruturação significativa.

Como funciona na prática

O gerenciamento de estado é uma tarefa complexa que pode ser abordada de maneira diferente, dependendo do framework escolhido.

Redux:

  • Centraliza a lógica do estado: O estado da aplicação é armazenado em um único lugar (o "store"), permitindo que os componentes obtenham e atualizem o estado.
  • Utiliza Actions: Os desenvolvedores criam "actions" para descrever as alterações no estado, que são então tratadas pelas "reduções".
  • Store: O store é a fonte centralizada do estado da aplicação. Ele armazena e fornece o estado atual da aplicação.
  • Connect: O componente conectado ao store recebe props dinâmicas com base no estado atual.

Context API:

  • Compartilhamento de contexto: A Context API permite que os componentes acessem valores de estados compartilhados através de um objeto de contexto.
  • Provider: O Provider é o componente que fornece o contexto para seus filhos, tornando-o possível acessar e atualizar o estado.
  • Consumer: Os Componentes filhos podem usar o contexto fornecido pelo Provider.

Zustand:

  • Armazenamento em memória: O Zustand armazena os dados do estado na memória, permitindo acesso rápido e eficiente.
  • Gerenciador de estados: O gerenciador de estados é a classe principal que permite criar, ler e atualizar o estado da aplicação.
  • Hook: Os componentes podem usar o Hook para acessar o estado armazenado no Zustand.

Exemplo real

Vamos considerar um exemplo simples de uma aplicação que gerencia a lista de tarefas pendentes. A aplicação deve ser capaz de adicionar, remover e listar as tarefas.

// Exemplo usando Redux:
import { createStore } from 'redux';
import { connect } from 'react-redux';

const initialState = {
  tasks: []
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'ADICIONAR_TAREFA':
      return { ...state, tasks: [...state.tasks, action.task] };
    case 'REMOVER_TAREFA':
      return { ...state, tasks: state.tasks.filter(task => task.id !== action.taskId) };
    default:
      return state;
  }
}

const store = createStore(reducer);

// Componente que usa o Redux para gerenciar as tarefas
function TarefaList(props) {
  const tasks = props.tasks.map((task, index) => (
    <li key={index}>{task.title}</li>
  ));

  return (
    <ul>
      {tasks}
    </ul>
  );
}

const mapStateToProps = state => ({ tasks: state.tasks });
const ConnectedTarefaList = connect(mapStateToProps)(TarefaList);

function App() {
  const dispatch = useDispatch();
  
  function adicionarTarefa(task) {
    dispatch({ type: 'ADICIONAR_TAREFA', task });
  }

  return (
    <div>
      <TarefaList />
      <button onClick={() => adicionarTarefa({ title: 'Nova tarefa' })}>
        Adicionar tarefa
      </button>
    </div>
  );
}

// Exemplo usando Context API:
import React, { createContext, useContext } from 'react';

const TasksContext = createContext();

function TarefaList() {
  const tasks = useContext(TasksContext);
  
  return (
    <ul>
      {tasks.map((task, index) => (
        <li key={index}>{task.title}</li>
      ))}
    </ul>
  );
}

function App() {
  const [tasks, setTasks] = useState([]);
  
  function adicionarTarefa(task) {
    setTasks([...tasks, task]);
  }

  return (
    <div>
      <TasksContext.Provider value={tasks}>
        <TarefaList />
      </TasksContext.Provider>
      <button onClick={() => adicionarTarefa({ title: 'Nova tarefa' })}>
        Adicionar tarefa
      </button>
    </div>
  );
}

// Exemplo usando Zustand:
import create from 'zustand';

const useStore = create(set => ({
  tasks: [],
  addTask: task => set(state => ({ ...state, tasks: [...state.tasks, task] })),
  removeTask: taskId => set(state => ({ ...state, tasks: state.tasks.filter(task => task.id !== taskId) }))
}));

function TarefaList() {
  const tasks = useStore((state) => state.tasks);
  
  return (
    <ul>
      {tasks.map((task, index) => (
        <li key={index}>{task.title}</li>
      ))}
    </ul>
  );
}

function App() {
  const addTask = useStore((state, action) => state.addTask(action.task));
  
  function adicionarTarefa(task) {
    addTask({ title: 'Nova tarefa' });
  }

  return (
    <div>
      <TarefaList />
      <button onClick={adicionarTarefa}>
        Adicionar tarefa
      </button>
    </div>
  );
}

Boas práticas

Armazenamento de estado separado

  • Em vez de usar useState para armazenar o estado, use uma biblioteca como Redux ou Context API para gerenciar o estado em nível de aplicação.
  • Isso permite que você tenha um único ponto de acesso ao estado e faça com que as mudanças sejam propagadas automaticamente a todas as partes da aplicação.

Encapsulamento do estado

  • Certifique-se de encapsular o estado dentro de uma função ou classe para evitar que ele seja acessado diretamente por outras partes da aplicação.
  • Isso ajuda a manter a consistência e a prevenir erros causados pela manipulação indevida do estado.

Armadilhas comuns

Injeção de dependências

  • Evite usar useContext em componentes que não precisam acessar o estado, pois isso pode levar à injeção de dependências indesejadas e dificultar a depuração.
  • Em vez disso, use um HOC (High-Order Component) para fornecer o contexto apenas quando necessário.

Escopo do estado

  • Se você estiver usando Context API ou Redux, certifique-se de que o escopo do estado esteja claro e seja fácil de entender para os desenvolvedores que trabalham no projeto.
  • Isso ajuda a evitar confusões sobre onde está armazenado o estado e como ele é atualizado.

Conclusão

Após analisar as opções para gerenciamento de estado, é importante destacar a importância da escolha certa para o projeto. Embora Redux seja uma ferramenta poderosa e amplamente utilizada, também pode ser excessivamente complexo para projetos menores. Por outro lado, Context API oferece uma abordagem mais leve, mas pode exigir mais cuidado ao lidar com a injeção de dependências.

Se você está começando um novo projeto, é recomendável considerar a utilização da Context API, pois ela fornece uma estrutura básica para gerenciar o estado sem exigir conhecimentos avançados. Já que o seu aplicativo está crescendo ou requer um gerenciamento de estado mais complexo, pode ser hora de reavaliar a necessidade de usar Redux.

Para projetos futuros, é essencial desenvolver habilidades em ambos os gerenciadores de estado, pois isso permitirá escolher a ferramenta certa para cada projeto e evitar a "armadilha do conhecimento" de estar atrelado a uma tecnologia específica.

Referências

  • Martin Fowler. _Injeção de dependência_. Disponível em: https://martinfowler.com/articles/injection.html Acesso: 2024.
  • MDN Web Docs. _Context API_. Disponível em: <https://developer.mozilla.org/pt-BR/docs/Web/API/Window/context> Acesso: 2024.
  • Redux Official Documentation. _Concepts: Stores_. Disponível em: https://redux.js.org/introduction/core-concepts#store Acesso: 2024.
  • Zustand Official Documentation. _Motivation_ e _Concepts_. Disponível em: https://github.com/pmndrs/zustand#motivation e <https://github.com/pmndrs/zustand#concepts> Acesso: 2024.
  • OWASP. _Injeção de dependência (ID)_. Disponível em: https://owasp.org/www-project-top-ten/2017/A9-Injection.md.html#sourcedetails Acesso: 2024.