Organização de pastas no React: Simplificando o caos com uma nova estrutura 🚀

Caio Borghi - Jun 3 '23 - - Dev Community

Salve!

No mundo da engenharia de software, existe um ditado que é mais ou menos assim "organização de pastas é diferente de arquitetura de software", entendo isso, mas mesmo assim, gosto de estudar diferentes estruturas e entender suas vantagens e desafios.

Então, vou compartilhar com vocês a forma que tenho organizado as pastas nos meus projetos React recentemente.

Algumas contextualizações

Eu venho de um passado com fortes influências do C# (.NET), onde o padrão, ao menos naquela época (2016-2018), sempre foi MVC (Model-View-Controller). Inquestionável em qualquer projeto, onde a aplicação era separada em 3 camadas:

Model - Objetos que seriam armazenados no banco de dados, hoje em dia conhecidos como Entidades.

View - Classes visuais da aplicação, nela ficavam formulários, páginas de login, página inicial, tudo que fosse visual, era a porta de entrada que apresentava os componentes e dados para os usuários, se conectava a um Controller e fazia uso de objetos (Models, ou na arquitetura MVVM ViewModels) de dados.

Controller - Meio de campo das aplicações, cuidava das validações, regras de negócio, acessava banco de dados, parseava Model, redirecionava para Views e, bem, controlava a aplicação.

Organização em pastas por tipo

Cada pasta armazenava arquivos de uma determinada camada. Com o tempo, criou-se também a camada de Services e, dependendo do Design Pattern, Repositories.

Nessa arquitetura, os arquivos eram separados por tipo, ficando com uma estrutura mais ou menos assim.

src/
  controllers/
    LoginController
    UserController
  models/
    LoginModel
    UserModel
  services/
    LoginService
    UserService
  views/
    LoginView
    UserProfileView
Enter fullscreen mode Exit fullscreen mode

Ao migrar para o React em 2018, encontrei um componente "reativo" construído à base de classes.

Com muitos

  • this
  • bind
  • constructor
  • this.setState({ ...this.state, count: this.state.count + 1 })

Decidi que usaria "MVC" no React.

components/
  LoginForm.js
models/
  User.js
services/
  userProfile.js
views/
  UserProfile.jsx
Enter fullscreen mode Exit fullscreen mode

Vantagens

É fácil decidir onde criar qualquer arquivo.

Se é uma página, você cria dentro de views, se faz parte da integração da UI com a API, é um service.

Caso seja um objeto, guardo dentro de models e se for algum componente compartilhado, dentro de components.

Desafios

Com o tempo, o tamanho das pastas cresce bastante!

Com React, é comum (e recomendado) ter muitos componentes.

A UI é composta de várias partes móveis, de diversos tamanhos, que são flexíveis, estilizáveis e, sobretudo, componentizáveis.

Com isso, fica difícil de encontrar arquivos, pior ainda de encontrar funções, você precisa procurar dentro de uma pasta enorme, comparando-os.

Mas o maior problema é que agora, para cada nova funcionalidade, você precisa criar X novos arquivos que ficarão espalhados em diferentes pastas, fica difícil de encontrá-los e, caso a aplicação não possua uma boa cobertura de testes unitários, mais difícil ainda de fazer mudanças.

Organização em pastas por funcionalidade

Para resolver isso, a estrutura de pastas foi reinventada em artigos e discussões.

Ela sugere que a aplicação deve ser quebrada não mais em tipos, mas agrupada por "funcionalidades" do sistema.

Ela segue a seguinte filosofia

Apagar uma funcionalidade deve ser tão fácil quanto apagar uma pasta.

Vantagens

Os arquivos ficam mais organizados, todos arquivos dentro de uma pasta estão diretamente conectados, é fácil de navegar pela estrutura modular de features.

components/
  button/
  input/
features/
  authentication/
    Login.js
    LoginForm.js
    useAuth.js
    AuthService.js    
    AuthSlice.js
  profile/
    Profile.js
    UserProfile.js
    ProfileService.js
Enter fullscreen mode Exit fullscreen mode

Desafios

Arquivos compartilhados.

  • Componentes não globais
  • Interfaces
  • Hooks

Arquivos compartilhados são um problema na estrutura de features porque é difícil decidir onde um arquivo deve morar, caso ele seja compartilhado por mais de uma funcionalidade, mas não seja algo global.

Caso exista um componente UserAvatar compartilhado pelas duas features, se optarmos por colocá-lo na pasta de components, isso significa que com o passar do tempo, essa pasta abrigará todos os componentes não-privados da aplicação.

E ficará grande e, mais uma vez, será difícil de encontrar relação entre os componentes.

Cada nova pasta que você criar, com o tempo, se for uma aplicação grande, vai crescer e se tornar desorganizada, pois serão pastas por tipo.

Além de que, caso você possua UserProfile, UserSettings e outras funcionalidades relacionadas a Usuário, elas também não ficarão agrupadas, mas soltas em diferentes partes da aplicação.

Ou seja, caso você queira deletar tudo relacionado à usuário na aplicação, você não conseguirá fazer isso apagando apenas uma pasta.

components/
  userAvatar/
features/
  profile/
    Profile.js
    UserProfile.js
    ProfileService.js
  settings/
    Settings.js
    UserSettings.js
Enter fullscreen mode Exit fullscreen mode

Proposta para solucionar os problemas

Depois de alguns anos e muitos projetos, debates e discussões, decidi propor uma abordagem híbrida de folder-by-feature-by-type, ou melhor, pasta-por-funcionalidade-por-tipo, e adicionar uma nova pasta para arquivos globais.

Percebi também que features acabava sendo um limitante excludente para partes da aplicação que, embora funcionais, nem sempre são entidades macro.

Resolvi adicionar um novo conceito (muito famoso e em alta no BackEnd) para a organização de pastas, o domínio.

Estrutura Pasta-Por-Domínio-Por-Tipo

Percebi que agrupar items por tipo resulta em uma grande quantidade de arquivos em uma mesma pasta e que isso resulta em quebra de agrupamento por proximidade.

Logo, sugiro que em níveis mais altos da aplicação, seja seguida uma estratégia de pasta-por-domínio, onde o domínio é uma seção da aplicação que representa uma entidade ou funcionalidade, visando evitar o acúmulo de arquivos "precoce" que, eventualmente, poluirão a estrutura de pastas.

E em níveis menores, apenas quando necessário, utilizar a abordagem pasta-por-tipo.

assets/
domains/
  /user
    /components
      UserAvatar.js
    /enums
    /features
      /profile
      /settings
    /types
    /hooks
global/
  /components
  /enums
  /hooks
  /routes
  /store
  /types
Enter fullscreen mode Exit fullscreen mode

Quando não é necessário

Quando há apenas 1 arquivo de determinado tipo em uma pasta, por exemplo:

domains/
  /user
    UserAvatar.js
    /features
      /profile
        UserProfile.js
        ProfileService.js
Enter fullscreen mode Exit fullscreen mode

Eu defendo que, caso a pasta conte com menos de 2 arquivos de um determinado tipo, como por exemplo UserAvatar.js, não precisa criar uma pasta por tipo para abrigar 1 único arquivo.

Neste caso, até a pasta domains seria desnecessária enquanto não houvesse um segundo domínio para a aplicação, ficando assim:

src/
  user/
    UserAvatar.js
    features/
      /profile
        UserProfile.js
        ProfileService.js
Enter fullscreen mode Exit fullscreen mode

A ideia dessa estratégia é ser fluída e utilizar o melhor dos dois mundos.

  • Organização por tipo, quando uma pasta ajuda a "agrupar" dois arquivos do mesmo tipo.
  • Organização por domínio nos níveis altos, para promover uma rápida movimentação entre seções da aplicação.

Comparar/Navegar por pastas é mais fácil do que comparar arquivos.

Quando criar uma pasta-por-tipo

Quando uma pasta abriga dois arquivos do mesmo tipo.

Esse número é uma sugestão, caso você queira mudar para 3, a fim de reduzir o número de pastas, é uma opção também.

Por exemplo, se o domínio user começar a incluir um grande número de hooks, como useUser.js, useUserPreferences.js, useUserSettings.js, etc., seria útil criar uma pasta hooks para manter todos esses arquivos relacionados juntos:

domains/
  /user
    /components
      UserAvatar.js
      UserBadge.js
    /hooks
      useUser.js
      useUserPreferences.js
      useUserSettings.js
    /features
      /profile
        UserProfile.js
        ProfileService.js

Enter fullscreen mode Exit fullscreen mode

Neste caso, a nova pasta ajuda a manter a estrutura do diretório clara e organizada, facilitando a localização dos arquivos relevantes quando necessário e permitindo minimizar (com a ajuda do IDE) seções ou tipos que não sejam interessantes para sua busca.

Conclusão

A estratégia proposta de pasta-por-domínio-por-tipo é um método híbrido visando equilibrar os benefícios das abordagens pasta-por-tipo e pasta-por-funcionalidade.

Com ela, a aplicação é dividida em domínios que representam entidades ou funcionalidades, e cada domínio é subdividido por tipo apenas quando necessário.

A flexibilidade deste modelo permite ajustes baseados nas necessidades específicas do projeto e da equipe.

A organização de pastas e arquivos é uma arte, e espero que esta proposta possa te inspirar a encontrar a estratégia ideal para os seus projetos.

Independentemente da estratégia escolhida, lembre-se que o objetivo final é sempre tornar o código mais legível, navegável e fácil de manter.

. . . . . . . . . . . . . . . . .