React + Controle de estado. Vamos organizar?

Ramon Prata
6 min readDec 2, 2020

--

Como você já deve saber, o React não é um framework e sim, uma biblioteca para criar interfaces de usuário. Isso quer dizer que, o React em si não vai e nem tem a pretensão (até então) de definir como sua aplicação deve ser estruturada/organizada e quer dizer também que, o React vai te dar maior liberdade para organizar sua aplicação da forma mais adequada às necessidades. Porém, parafraseando uncle Ben

com grandes poderes, vem grandes responsabilidades

Dito isso, este artigo não tem como propósito entregar uma receita de bolo definitiva de como organizar sua aplicação React, mas sim, propor algumas ideias.

Aqui irei falar somente das propostas, se você quiser saber mais detalhes das considerações que levaram a essas decisões, tenho uma versão mais detalhada desse artigo explicando alguns problemas, exemplos de cenários e snipets de código. Está tudo nesse repositório no meu Github

Agora, vamos ao que interessa..

Numa aplicação React, quem é responsável pelo que?

Bom, já que falamos de reponsabilidade, acredito que esse seja o cerne dos problemas de organização de uma aplicação React, não separamos bem as reponsabilidades. E isso é o que os frameworks como Angular, ao meu ver, fazem bem. O React é uma lib de UI com escopo de lidar com a renderização de Componentes. Porém, quando essa ideia não fica clara, acabamos trazendo para os componentes várias outras responsabilidades

  • componentes lidando diretamente com chamadas de APIs
  • componentes validando coisas, tratando exceções
  • componentes tendo que “entender” regras de negócio
  • componentes fazendo muita manipulação de dados
  • componentes lidando diretamente com recursos do browser

O React é uma lib declarativa, isso significa que quando construímos um componente, nós devemos preocupar mais com O QUE e deixar para o React lidar com O COMO renderizar. Seria bom se usássemos essa mesma abordagem — O QUE/COMO — para separarmos o que é responsabilidade do componente e o que não é e assim mantermos nossos componentes menos imperativos e mais declarativos possível, não só na parte da renderização (que já é abstraída pelo React). Para isso precisamos separar responsabilidades, começando pela estrutura de pastas.

Estrutura de pastas — Proposta — FeatureResponsability

Agrupamento por Features

  • Cada Feature deve conter todos os arquivos correlatos
  • Dentro da pasta da Feature haverá algumas subpastas e arquivos para separar responsabilidades. Porém, evite mais de 3 níveis de aninhamento, recomendação.

Responsabilidades — FeatureStore

  • Path: src/Features/pastaFeature/store
  • O nome Store é genérico, mas você pode chamar essa subpasta pelo nome da lib que estiver usando para controle de estado global. Ex.: redux
  • Aqui vai todos os arquivos relacionados a controle de estado “global” da Feature: actions, operations, reducer.
  • store/nomeFeatureActions: para ações síncronas que disparam um único dispatch
  • store/nomeFeatureOperations: É possível que uma ação dispare múltiplos dispatches e/ou faça algo assíncrono. Por serem “actions especiais”, vamos chama-las de Operations
  • store/nomeFeatureReducer: É onde será feito a atualização de fato do estado da Feature, dado a ocorrência de alguma action
  • Nos próximos pontos, quando mencionar Store, estarei me referindo a esses três arquivos.

Responsabilidades — FeatureHooks

  • Path: src/Features/pastaFeature/hooks
  • Pode ser que uma mesma lógica que envolva uso de hooks do React ou de terceiros seja útil em mais de um componente da Feature. Nesse caso extraia a lógica para hooks customizados que serão usados somente no contexto da Feature

Responsabilidades — FeatureViews

  • Path: src/Features/pastaFeature/views
  • Aqui vai todos os arquivos referentes a renderização, ou seja, componentes da Feature. Você pode renomear essa pasta para Componentes, por exemplo. Mas não recomendo porque teremos uma pasta Componentes compartilhada.
  • A reponsabilidade do componente deve se conter na renderização. Deixe os demais arquivos da Feature lidarem com as complexidades do COMO fazer — chamadas de APIs, regras de negócio, manipulação de dados, validações, etc.
  • Tente manter seu componente menos imperativo e mais declarativo
  • Use extensão .jsx para componentes

Responsabilidades — FeatureService

  • Path: src/Features/pastaFeature/nomeFeatureService
  • Responsável pela comunicação da Feature com fonte de dados seja para consumir ou persistir: chamadas à APIs, banco de dados local (no caso de aplicações PWA).
  • Abstrai dos Componentes e da Store a complexidade de COMO é feito o input/output de dados

Responsabilidades — FeatureManager

  • Path: src/Features/pastaFeature/nomeFeatureManager
  • Responsável por intermediar a comunicação de Componentes e Store com a Service
  • Abstrai dos Componentes e Store a complexidade de regras de negócio aplicadas sobre input/output de dados da Feature. Os Componentes e a Store não precisam saber COMO dados são transformados

Responsabilidades — FeatureUtils

  • Path: src/Features/pastaFeature/nomeFeatureUtils
  • Funções úteis que só fazem sentido no contexto da Feature.
  • Abstrai dos demais arquivos da Feature (Componentes, Store, Manager) a complexidade de funções de granularidade fina. As conhecidas “funções de escovar bit”.

Responsabilidades — index.js

  • Path: src/Features/pastaFeature/index.js
  • Responsável por exportar para aplicação somente o que é necessário da Feature
  • Não deve conter implementação.

Responsabilidades — Tests

  • Path: uma pasta tests para cada nível da Feature
  • src/Features/pastaFeature/tests: para testes unitários de FeatureManager, FeatureUtils, FeatureService
  • src/Features/pastaFeature/store/tests: para testes unitários de FeatureActions, FeatureOperations, FeatureReducer
  • src/Features/pastaFeature/views/tests: para testes unitários de Componentes

Pasta Shared

É claro que numa aplicação real teremos coisas que serão genéricas o suficiente que possam ser usadas em várias Features (componentes, hooks, funções úteis, constantes, etc.).

  • Path: src/Shared

Agora que já separamos as responsabilidades, vamos ver como essas partes interagem entre si.

Fluxo Geral

  • FeatureService: é o único que comunica com a fonte de dados, seja APIs ou repositório local (aplicações PWA)
  • FeatureManager: cria uma ponte de comunicação entre store/componentes e o FeatureService, aplicando regras de negócio sobre o input/output de dados.
  • Store (nesse caso Redux): comunica com FeatureManager; Desce estados paras os Componentes; Recebe ações vindas dos Componentes
  • Componentes: comunicam com Store consumindo o estado (FeatureReducer ) ou subindo ações (FeatureActions).
  • Componentes: comunicam com FeatureManager para input/output de dados diretamente nos Componentes. Dados que não precisam estar no estado global, somente no escopo do componente.

Uma explicação mais detalhada desse fluxo, com exemplos de código, você vai encontrar no repositório mencionado no início do artigo.

O uso — Vantagens e Sugestões —

O uso dessa estrutura em algumas aplicações apresentou resultados bem positivos

  • Melhoria na performance da aplicação
  • Redução no tamanho do bundle da aplicação
  • Potencialização da escalabilidade da aplicação
  • Melhoria na organização do código
  • Facilidade no entendimento da aplicação
  • Redução na curva de onboarding de novos membros na equipe
  • Aumento do reuso de código
  • Redução da quantidade de testes unitários
  • Redução no tempo para manutenibilidade da aplicação

Sugestões

Algumas adaptações foram feitas/sugeridas pelos times de devs que usaram essa proposta em seus projetos. Talvez elas façam sentido para o seu projeto também

  • FeatureManager e FeatureService: serem colocadas numa subpasta da Feature, em vez de ficar na pasta raiz. Ex.: /pastaFeature/api/FeatureService
    /pastaFeature/api/FeatureManager
  • FeatureService: mudar nome para FeatureRepository
  • FeatureService: não ficar dentro da pasta da Feature. Em vez disso, ser um recurso compartilhado numa subpasta na Shared. Ex.:src/Shared/api/FeatureService
  • Componentes: Para cada componente, seja da Feature ou compartilhado, criar uma pasta e não ser só o Arquivo.jsx em si

Considerações finais

Portanto, como foi dito, a estrutura apresentada aqui é uma proposta para te ajudar a pensar em como organizar sua aplicação React. Explore-a e adapte conforme às necessidades. Mas, tenha sempre que o React é uma lib pra renderização de componentes e que Componentes não devem ser responsáveis por tudo na sua aplicação, sendo assim, devemos separar as responsabilidades.

--

--

Ramon Prata
Ramon Prata

Responses (2)