Muito além de /components e /pages
Apesar de utilizar estruturas de pastas do React, esse artigo vai servir pra qualquer projeto e em qualquer linguagem, tire proveito do mindset e faça bom proveito das dicas :heart:
Bom, como comentado no meu post "Construindo um frontend flexível", estou com uma experiência em aplicações multitenants. Chega num ponto em que cada projeto seu já está com umas 72~80k linhas de código e você não sabe onde está cada coisa porque sua estrutura inicial não favorecia a ser algo escalar.
Isso é um problema absurdo, por que acaba-se andando muito entre diretórios e você não sabe onde achar ou sabe onde achar e demora bastante para andar entre seus arquivos. A própria estrutura padrão de src/{components,pages,redux,actions,helpers,utils,hooks}
acaba não lhe favorecendo a escalar e saber onde está cada coisa
Entre conversas, leituras do DDD (Domain Driven Design) e podcasts, acabei adotando uma estrutura de projeto um pouco diferente do comum, mas que faz muito sentido. Talvez você possa fazer alguma associação com ducks (se vier da comunidade React), mas acho que a parada é um pouco diferente. Um nome que ouvi bastante para isso foi Scope named packages
. Se liga numa estrutura básica
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
├── build ├── docs ├── package.json ├── public ├── scripts └── src ├── clientes │ ├── components │ │ └── HomeClient.tsx │ ├── DeleteClient.ts │ ├── GetClients.ts │ ├── hooks │ │ └── useClients.ts │ ├── SaveClient.ts │ └── UpdateClient.ts ├── colaboradores ├── dashboard ├── helpers ├── produtos └── store
Nesse modelo, cada entidade do seu projeto será um diretório, e dentro dele você terá tudo que for refente a essa entidade. Ações de top level e classes de modelo são colocadas na raiz de cada diretório referente a entidade. Ações caracterizadas como utils/helpers
e components/pages
são colocados em sub diretórios, como mostrado na árvore acima.
Caso você use Redux, uma particularidade que decidi adotar:
Cada ação top level (ações compartilhadas por mais de um componente ou função, ações do reducer e o próprio reducer) fica no mesmo arquivo do seu reducer, assim facilitando o controle de estado daquele fluxo. Exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// GetClients.ts // Padrão utilizando redux-sagas export const GetClients = () => ({ type: Actions.Client.GetAll }); const initialState = { clients: [], loading: false }; type State = typeof initialState; function Action(action:){ const response = yield call(fetch, "https://api.awesomeapi.io/"); if (response.status === 200) { const clients = await response.json(); yield put({ type: Actions.Client.GetAllSagas, clients }) } else if (response.status === 404) { Notification("Mensagem de não existe pro usuário"); } } type SagasAction = { clients: Client[] } const Reducer = CreateReducer(initialState, { [Actions.Client.GetAllSagas]: (state: State, action: SagasAction) => ({ ...state, clients: action.clients }) }); export default { Reducer, Action };
Você pode conferir o CreateReducer aqui
Esse foi um pequeno exemplo de como você pode organizar seus reducers.
Lógico que nem tudo irá ficar somente contido dentro de pacotes nomeados por suas entidades, as vezes algumas funções, componentes, tipos, classes de modelo e outros serão reutilizados. Mas para ter uma estrutura escalar é importante tentar manter a altura da árvore de diretórios de até 2. Pode parecer bobeira, mas você vai me agradecer por se manter nessa regra.
Uma outra prática que não é muito interessante é criar uma pasta para um componente e nessa pasta ter somente o index.{jsx?,tsx?}
. Nesses casos, melhor criar o arquivo com o nome do componente fora da pasta.
Um erro que acabei cometendo por falta de tempo e recurso foi manter a minha biblioteca de componentes dentro do projeto, o que aumentou demais a codebase, e sem necessidade. Agora com um pouco mais de tempo livre estou quebrando partes do código que poderão ser reaproveitadas por outros projetos. Com isso, reduzo o tamanho da minha codebase e posso dividir melhor a equipe para trabalhar em repositórios separados.
Um projeto para o uso de clientes e empresas foi quebrado da seguinte maneira
1 2 3 4 5
SUPER-PROJETO-GIGANTE _| | |_ cliente shared empresa ____________________|||_____________________ componentes model services-hooks-actions
Um grande projeto maior foi quebrado em 3, uma área de clientes, uma de empresa e a área para cadastro e faq de ambos. Somente com essa quebra, o bundle size pode ser reduzido de 620KB para 2 bundles de ~340KB (isso porque tive que ter uma replicação de código para evitar a quebra de alguns pacotes). Quebrando ainda mais a lógica e componentes, conseguimos uma redução para 290KB (eliminando código duplicado, actions não usadas, simplificando a lógica e reescrevendo alguns componentes que ainda utilizávamos de outras bibliotecas). O projeto que possuia ~80k linhas agora está com ~42K linhas. FUCKING REDUÇÃO.
Óbviamente essa redução tem seus "mistérios", pois parte da lógica foi abstraída para outros repositórios, mas o projeto principal se tornou mais fácil de manter, e os subprojetos agora podem ser mantidos por uma pessoa sem muita dificuldade.
Óbvio que isso não é uma estrutura que serve para qualquer coisa, é algo situacional que poderá ou não vir a calhar para você. Listando meus prós e contras dessa arquitetura
Isso tudo que foi falado não é um padrão, não irá gerar tendência, é apenas uma forma que deu certo em um projeto grande e quis compartilhar com você.
Sim, eu disse você, a única pessoa que lê o meu blog :heart:
E como de costume...