React
Share on facebook
Share on twitter
Share on linkedin

React + Controle de estado: vamos organizar?

Como você já deve saber, o React não é um frameworkmas 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, vêm 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, vou falar somente das propostas. Se você quiser saber mais detalhes sobre as considerações que levaram a essas decisões, tenho uma versão mais detalhada deste artigo, explicando alguns problemas, exemplos de cenários e snipets de código. Está tudo neste repositório no meu Github. 

Agora, vamos ao que interessa... 

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

Bom, já que falamos de responsabilidade, acredito que esse seja o cerne dos problemas de organização de uma aplicação React: não separamos bem as responsabilidades. E isso é o que os frameworks como Angularao meu ver, fazem bem.  

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. 

React é uma lib declarativa. Ou seja, 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 é. Dessa forma, manteríamos 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. 

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, se encontrarão todos os arquivos relacionados a controle de estado “global” da Featureactionsoperationsreducer. 
  • store/nomeFeatureActionspara 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 chamá-las de Operations. 
  • store/nomeFeatureReducerÉ onde será feita a atualização de fato do estado da Featuredado 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, se encontrarão todos os arquivos referentes à renderização, ou seja, componentes da Feature. Você pode renomear essa pasta para Componentes, por exemplo. Mas não recomendo, uma vez que teremos uma pasta Componentes compartilhada. 
  • A responsabilidade do componente deve está contida 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 Store com a Service. 
  • Abstrai dos Componentes Store a complexidade de regras de negócio aplicadas sobre input/output de dados da FeatureOs 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 FeatureManagerFeatureUtilsFeatureService. 
  • src/Features/pastaFeature/store/tests: para testes unitários de FeatureActionsFeatureOperationsFeatureReducer. 
  • 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 para poderem ser utilizadas 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 ,Redux): comunica com FeatureManager, desce estados para os Componentes, e 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ê encontra 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 no seu projeto: 

  • FeatureManager e FeatureServiceserem colocadas numa subpasta da Feature, em vez de ficar na pasta raiz. Ex.: /pastaFeature/api/FeatureService;
    /pastaFeature/api/FeatureManager. 
  • FeatureServicemudar nome para FeatureRepository. 
  • FeatureServicenã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 e a adapte conforme as suas necessidades.

Mas, tenha em mente: o React é uma lib para 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. 

Tá na dúvida?

[email protected]

R. Antônio de Albuquerque, 330 – 14° andar
Savassi, Belo Horizonte – MG, 30112-010