O material, cuja tradução publicamos hoje, revela as abordagens usadas por seu autor na estruturação de aplicativos React. Em particular, discutiremos aqui a estrutura de pastas usada, o nome das entidades, os locais onde os arquivos de teste estão localizados e outras coisas semelhantes.
Um dos recursos mais agradáveis do React é que essa biblioteca não força o desenvolvedor a cumprir rigorosamente certas convenções relacionadas à estrutura do projeto. Muito disso permanece a critério do programador. Essa abordagem é diferente da, digamos, adotada nas estruturas Ember.js ou Angular. Eles oferecem aos desenvolvedores mais recursos padrão. Essas estruturas fornecem convenções sobre a estrutura de projetos e regras para nomear arquivos e componentes.

Pessoalmente, gosto da abordagem adotada pelo React. O fato é que eu prefiro controlar algo sozinho, sem depender de certos "acordos". No entanto, existem muitas vantagens na abordagem de estruturação de projetos que a Angular oferece. A escolha entre liberdade e regras mais ou menos rígidas se resume ao que está mais perto de você e de sua equipe.
Ao longo dos anos de trabalho com o React, tentei várias maneiras diferentes de estruturar aplicativos. Algumas das idéias que apliquei foram mais bem-sucedidas do que outras. Portanto, aqui vou falar sobre tudo que se mostrou bem na prática. Espero que você encontre aqui algo que seja útil para você.
Não estou tentando mostrar aqui uma maneira "apenas correta" de estruturar aplicativos. Você pode pegar algumas das minhas idéias e alterá-las para atender às suas necessidades. Você pode discordar de mim continuando a trabalhar como antes. Equipes diferentes criam aplicativos diferentes e usam meios diferentes para atingir seus objetivos.
É importante observar que, se você olhar para o site
Thread , do qual participo no desenvolvimento, e para o dispositivo de sua interface, encontrará lugares em que essas regras sobre as quais falarei não são respeitadas. O fato é que quaisquer "regras" na programação devem ser tomadas apenas como recomendações e não como padrões abrangentes válidos em qualquer situação. E se você acha que algum tipo de “regra” não combina com você, você, com o objetivo de melhorar a qualidade do que está trabalhando, deve encontrar forças para se desviar dessas “regras”.
Na verdade, agora, sem mais delongas, ofereço minha história sobre a estruturação de aplicativos React.
Não se preocupe muito com as regras.
Talvez você decida que a recomendação de que você não se preocupa muito com as regras parece estranha no início de nossa conversa. Mas é exatamente isso que quero dizer quando digo que o principal erro que os programadores têm em termos de observação das regras é que os programadores atribuem muita importância às regras. Isto é especialmente verdade no início do trabalho em um novo projeto. No momento da criação do primeiro
index.jsx
simplesmente impossível saber o que é melhor para este projeto. À medida que o projeto se desenvolve, você naturalmente encontrará algum tipo de estrutura de arquivos e pastas, o que provavelmente será muito bom para esse projeto. Se, durante a continuação do trabalho, a estrutura existente não for bem-sucedida, ela poderá ser melhorada.
Se você ler isso e se achar pensando que não há nada em seu aplicativo que esteja sendo discutido, isso não será um problema. Cada aplicativo é único, não há duas equipes de desenvolvimento absolutamente idênticas. Portanto, cada equipe, trabalhando em um projeto, chega a alguns acordos em relação à sua estrutura e métodos de trabalho. Isso ajuda os membros da equipe a trabalhar produtivamente. Não se esforce para, depois de aprender como alguém está fazendo algo, apresente isso imediatamente. Não tente introduzir em seu trabalho o que é chamado em certos materiais e, mesmo assim, a "maneira mais eficaz" de resolver um problema. Sempre adotei e adotei a estratégia a seguir em relação a essas recomendações. Eu tenho meu próprio conjunto de regras, mas lendo sobre como os outros agem em determinadas situações, escolho o que me parece bem-sucedido e adequado para mim. Isso leva ao fato de que, com o tempo, meus métodos de trabalho melhoram. Ao mesmo tempo, não tenho choques e não há desejo de reescrever tudo do zero.
Componentes importantes estão localizados em pastas separadas
A abordagem para colocar arquivos de componentes nas pastas em que cheguei é que os componentes que podem ser considerados "importantes", "básicos", "principais" no contexto do aplicativo são colocados em pastas separadas. Essas pastas, por sua vez, estão localizadas na pasta de
components
. Por exemplo, se estamos falando de um aplicativo para uma loja de eletrônicos, o componente
<Product>
usado para descrever o produto pode ser reconhecido como um componente semelhante. Aqui está o que eu quero dizer:
- src/ - components/ - product/ - product.jsx - product-price.jsx - navigation/ - navigation.jsx - checkout-flow/ - checkout-flow.jsx
Nesse caso, os componentes "secundários" usados apenas por determinados componentes "principais" estão localizados na mesma pasta que esses componentes "principais". Essa abordagem se provou na prática. O fato é que, devido à sua aplicação, uma certa estrutura aparece no projeto, mas o nível de aninhamento de pastas não é muito grande. Sua aplicação não leva à aparência de algo como
../../../
nos comandos de importação de componentes, não dificulta a movimentação pelo projeto. Essa abordagem permite criar uma hierarquia clara de componentes. Esse componente, cujo nome corresponde ao nome da pasta, é considerado "básico". Outros componentes localizados na mesma pasta servem para dividir o componente "base" em partes, o que simplifica o trabalho com o código desse componente e seu suporte.
Embora eu seja um defensor da presença de uma certa estrutura de pastas no projeto, acredito que o mais importante é a seleção de bons nomes de arquivos. As próprias pastas são menos importantes.
Usando subpastas para subcomponentes
Uma das desvantagens da abordagem acima é que seu uso pode levar ao aparecimento de pastas de componentes "básicos" que contêm muitos arquivos. Considere, por exemplo, o componente
<Product>
. Arquivos CSS serão anexados a ele (falaremos sobre eles mais adiante), arquivos de teste, muitos subcomponentes e, possivelmente, outros recursos - como imagens e ícones SVG. Esta lista de "adições" não é limitada. Tudo isso cairá na mesma pasta que o componente "base".
Eu realmente não me importo com isso. Isso serve para mim se os arquivos tiverem nomes bem pensados e se puderem ser encontrados com facilidade (usando as ferramentas de busca de arquivos no editor). Nesse caso, a estrutura da pasta desaparece em segundo plano.
Aqui está um tweet sobre este tópico.
No entanto, se você preferir que seu projeto tenha uma estrutura mais extensa, não há nada difícil em mover subcomponentes para suas próprias pastas:
- src/ - components/ - product/ - product.jsx - ... - product-price/ - product-price.jsx
Os arquivos de teste estão localizados no mesmo local que os arquivos dos componentes sob teste.
Começamos esta seção com uma recomendação simples, que é a de que os arquivos de teste devem ser colocados no mesmo local que os arquivos com o código que foram verificados com a ajuda deles. Também falarei sobre como prefiro estruturar os componentes, tentando garantir que eles fiquem próximos um do outro. Mas agora posso dizer que acho conveniente colocar os arquivos de teste nas mesmas pastas que os arquivos de componentes. Nesse caso, os nomes dos arquivos com os testes são idênticos aos nomes dos arquivos com o código.
.test
nomes dos testes, antes da extensão do nome do arquivo, o sufixo
.test
adicionado apenas:
- Nome do arquivo do componente:
auth.js
- Nome do arquivo de teste:
auth.test.js
Essa abordagem tem vários pontos fortes:
- Isso facilita a localização de arquivos de teste. À primeira vista, você pode entender se há um teste para o componente com o qual estou trabalhando.
- Todos os comandos de importação necessários são muito simples. No teste, para importar o código testado, você não precisa criar estruturas que descrevam, por exemplo, a saída da pasta
__tests__
. Essas equipes parecem extremamente simples. Por exemplo, assim: import Auth from './auth'
.
Se tivermos alguns dados usados durante o teste, por exemplo, algo como zombarias de solicitações de API, os colocaremos na mesma pasta em que o componente e seu teste já estão. Quando tudo o que é necessário está em uma pasta, isso contribui para o crescimento da produtividade. Por exemplo, se você usar uma estrutura de pastas ramificadas e o programador tiver certeza de que existe um determinado arquivo, mas não conseguir lembrar o nome, o programador precisará procurar esse arquivo em vários subdiretórios. Com a abordagem proposta, basta olhar para o conteúdo de uma pasta e tudo ficará claro.
Módulos CSS
Eu sou um grande fã de
módulos CSS . Descobrimos que eles são ótimos para criar regras modulares de CSS para componentes.
Além disso, gosto muito da tecnologia de
componentes com
estilo . No entanto, no decorrer do trabalho em projetos nos quais muitos desenvolvedores participaram, verificou-se que a presença de arquivos CSS reais no projeto aumenta a usabilidade.
Como você provavelmente já adivinhou, nossos arquivos CSS estão localizados, como outros arquivos, ao lado dos arquivos componentes, nas mesmas pastas. Isso simplifica bastante o movimento entre arquivos quando você precisa entender rapidamente o significado de uma classe.
Uma recomendação mais geral, cuja essência permeia todo esse material, é que todo o código relacionado a um determinado componente seja mantido na mesma pasta em que esse componente está localizado. Longe vão os dias em que pastas separadas foram usadas para armazenar código CSS e JS, código de teste e outros recursos. O uso de estruturas complexas de pastas complica o movimento entre arquivos e não tem nenhum benefício óbvio, exceto pelo fato de ajudar a "organizar o código". Mantenha os arquivos interconectados na mesma pasta - isso significa gastar menos tempo movendo-se entre as pastas durante o trabalho.
Até criamos um carregador Webpack para CSS, cujos recursos correspondem aos recursos de nosso trabalho. Ele verifica os nomes de classe declarados e gera um erro no console se nos referirmos a uma classe que não existe.
Quase sempre, apenas um código de componente é colocado em um arquivo
Minha experiência mostra que os programadores geralmente aderem estritamente à regra de que o código para um e apenas um componente React deve estar em um arquivo. Ao mesmo tempo, apoio totalmente a idéia de que não vale a pena colocar muitos componentes em um arquivo (imagine as dificuldades de nomear esses arquivos!). Mas acredito que não há nada errado em colocar o código de um determinado componente "grande" e o código do componente "pequeno" associado a ele no mesmo arquivo. Se esse movimento ajudar a preservar a pureza do código, se o componente "pequeno" não for muito grande para colocá-lo em um arquivo separado, isso não prejudicará ninguém.
Por exemplo, se eu criar um componente
<Product>
e precisar de um pequeno pedaço de código para exibir o preço, posso fazer isso:
const Price = ({ price, currency }) => ( <span> {currency} {formatPrice(price)} </span> ) const Product = props => {
A coisa boa dessa abordagem é que eu não precisei criar um arquivo separado para o componente
<Price>
e que esse componente esteja disponível exclusivamente para o componente
<Product>
. Como não exportamos esse componente, ele não pode ser importado para outro local do aplicativo. Isso significa que, quando perguntado se deve colocar
<Price>
em um arquivo separado, você pode dar uma resposta clara e positiva se precisar importá-lo para outro lugar. Caso contrário, você pode fazer isso sem colocar o código
<Price>
em um arquivo separado.
Pastas separadas para componentes universais
Recentemente, usamos componentes universais. Eles, de fato, formam nosso sistema de design (que planejamos publicar um dia), mas até agora começamos pequenos - com componentes como
<Button>
e
<Logo>
. Um componente é considerado "universal" se não estiver vinculado a uma parte específica do site, mas é um dos elementos básicos da interface do usuário.
Componentes semelhantes estão localizados em sua própria pasta (
src/components/generic
). Isso simplifica bastante o trabalho com todos os componentes universais. Eles estão em um só lugar - é muito conveniente. Com o tempo, à medida que o projeto cresce, planejamos desenvolver um guia de estilo (somos grandes fãs do
react-styleguidist ) para simplificar ainda mais o trabalho com componentes universais.
Usando aliases para importar entidades
A estrutura de pastas relativamente plana em nossos projetos garante que os comandos de importação não tenham estruturas muito longas como
../../
. Mas é difícil ficar sem eles. Portanto, usamos o
babel-plugin-module-resolvedor para configurar aliases que simplificam os comandos de importação.
Você pode fazer o mesmo com o Webpack, mas, graças ao plug-in Babel, os mesmos comandos de importação podem funcionar em testes.
Configuramos isso com um par de aliases:
{ components: './src/components', '^generic/([\\w_]+)': './src/components/generic/\\1/\\1', }
O primeiro é extremamente simples. Permite importar qualquer componente, iniciando o comando com a palavra
components
. Na abordagem normal, os comandos de importação são mais ou menos assim:
import Product from '../../components/product/product'
Em vez disso, podemos escrevê-los assim:
import Product from 'components/product/product'
Ambos os comandos importam o mesmo arquivo. Isso é muito conveniente, pois permite que você não pense na estrutura da pasta.
O segundo alias é um pouco mais complicado:
'^generic/([\\w_]+)': './src/components/generic/\\1/\\1',
Nós usamos regex aqui. Ele encontra comandos de importação que começam com
generic
(o sinal
^
no início da expressão permite selecionar apenas os comandos que começam com
generic
) e captura o que é depois de
generic/
no grupo. Depois disso, usamos o fragmento capturado (
\\1
) na construção
./src/components/generic/\\1/\\1
.
Como resultado, podemos usar os comandos de importação para componentes universais desse tipo:
import Button from 'generic/button'
Eles são convertidos para os seguintes comandos:
import Button from 'src/components/generic/button/button'
Este comando, por exemplo, serve para importar um arquivo JSX que descreve um botão universal. Fizemos tudo isso porque essa abordagem simplifica bastante a importação de componentes universais. Além disso, será útil se decidirmos alterar a estrutura dos arquivos do projeto (isso, conforme nosso sistema de design está crescendo, é bem possível).
Aqui, gostaria de observar que você deve ter cuidado ao trabalhar com pseudônimos. Se você tiver apenas alguns deles, e eles foram projetados para resolver problemas de importação padrão, tudo está bem. Mas se você tiver muitos deles, eles podem trazer mais confusão do que benefícios.
Pasta lib universal para utilitários
Gostaria de recuperar todo o tempo que gastei tentando encontrar o lugar perfeito para o código que não é o componente. Compartilhei tudo isso de acordo com princípios diferentes, destacando o código de utilitários, serviços e funções auxiliares. Tudo isso tem tantos nomes que eu não vou mencionar todos eles. Agora não estou tentando descobrir a diferença entre o "utilitário" e a "função auxiliar" para encontrar o lugar certo para um determinado arquivo. Agora eu uso uma abordagem muito mais simples e compreensível: tudo isso cai em uma única pasta
lib
.
A longo prazo, o tamanho dessa pasta pode ser tão grande que você precisará estruturá-la de alguma forma, mas isso é completamente normal. É sempre mais fácil equipar algo com uma certa estrutura do que se livrar de erros de estrutura excessiva.
No nosso projeto Thread, a pasta
lib
contém cerca de 100 arquivos. Eles são divididos aproximadamente igualmente em arquivos que contêm a implementação de certos recursos e em arquivos de teste. Não causou nenhuma dificuldade em encontrar os arquivos necessários. Graças aos
lib/name_of_thing
pesquisa inteligentes incorporados à maioria dos editores, quase sempre preciso digitar algo como
lib/name_of_thing
, e o que eu preciso é encontrado.
Além disso, temos um alias que simplifica a importação da pasta
lib
, permitindo que você use comandos desse tipo:
import formatPrice from 'lib/format_price'
Não se assuste com as estruturas de pastas simples que podem causar o armazenamento de vários arquivos em uma pasta. Normalmente, essa estrutura é tudo o que é necessário para um determinado projeto.
Ocultando bibliotecas de terceiros por trás de APIs nativas
Eu realmente gosto do sistema de monitoramento de bugs
Sentry . Eu costumava usá-lo no desenvolvimento de partes de aplicativos para servidor e cliente. Com sua ajuda, você pode capturar exceções e receber notificações sobre sua ocorrência. Essa é uma ótima ferramenta que nos permite acompanhar os problemas encontrados no site.
Sempre que uso uma biblioteca de terceiros em meu projeto, penso em como fazê-lo para que, se necessário, possa ser substituído o mais facilmente possível por outra coisa. Freqüentemente, como no mesmo sistema Sentry de que realmente gostamos, isso não é necessário. Mas, apenas no caso, nunca é demais pensar em uma maneira de evitar o uso de um determinado serviço ou em uma maneira de alterá-lo para outra coisa.
A melhor solução para esse problema é desenvolver sua própria API que oculte as ferramentas de outras pessoas. Isso é como criar um módulo
lib/error-reporting.js
que exporta a função
reportError()
. O núcleo deste módulo usa o Sentry. Mas o Sentry é importado diretamente apenas neste módulo e em nenhum outro lugar. Isso significa que substituir o Sentry por outra ferramenta parecerá muito simples. Para fazer isso, basta alterar um arquivo em um só lugar. Enquanto a API pública desse arquivo permanecer inalterada, o restante do projeto nem saberá que, ao chamar
reportError()
, não é o Sentry que é usado, mas outra coisa.
Observe que a API pública do módulo é chamada de funções que ele exporta e seus argumentos. Eles também são chamados de interface pública do módulo.
Usando PropTypes (ou ferramentas como TypeScript ou Flow)
Quando faço programação, penso em três versões de mim mesmo:
- Jack do passado e o código que ele escreveu (às vezes código duvidoso).
- Jack de hoje e o código que ele escreve agora.
- Jack do futuro. Quando penso nesse futuro, pergunto a mim mesmo o presente sobre como escrever código que facilitará minha vida no futuro.
Pode parecer estranho, mas achei útil, pensando em como escrever código, faça a seguinte pergunta: "Como será percebido em seis meses?".
Uma maneira simples de se apresentar e ser mais produtivo é especificar os tipos de propriedades (
PropTypes
) usadas pelos componentes. Isso economizará tempo procurando possíveis erros de digitação. Isso o protegerá de situações nas quais, usando o componente, são aplicadas propriedades dos tipos errados ou elas esquecem completamente a transferência de propriedades. No nosso caso, a
regra eslint-react / prop-types é um bom lembrete da necessidade de usar
PropTypes
.
Se você for ainda mais longe, é recomendável descrever as propriedades com a maior precisão possível. Por exemplo, você pode fazer isso:
blogPost: PropTypes.object.isRequired
Mas seria muito melhor fazer isso:
blogPost: PropTypes.shape({ id: PropTypes.number.isRequired, title: PropTypes.string.isRequired,
No primeiro exemplo, a verificação mínima necessária é executada. No segundo, o desenvolvedor recebe informações muito mais úteis. Eles serão muito úteis, por exemplo, se alguém se esquecer de um determinado campo usado no objeto.
Bibliotecas de terceiros são usadas apenas quando são realmente necessárias.
Essa dica é mais relevante do que nunca com o advento dos
ganchos React . Por exemplo, eu participei de uma grande alteração de uma das partes do site Thread e decidi prestar atenção especial ao uso de bibliotecas de terceiros. , , . ( ), .
, React-. — , , React API Context, .
, , Redux, . , ( , ). , , , .
— , , . .
, . , . , « ». , , , . . «» - , . , , .
Redux. . , . , ,
user_add_to_cart
, . . , , Redux, . , Redux , .
, , , , :
- , , . .
- , , . , .
- , - , . , , .
- , . , , . , , , «» .
, , API Context
- .
, (, ,
). : , .
, , , . :
const wrapper = mount( <UserAuth.Provider value=> <ComponentUnderTest /> </UserAuth.Provider> )
:
const wrapper = mountWithAuth(ComponentUnderTest, { name: 'Jack', userId: 1, })
:
- . — , , , .
- —
mountWithAuth
. , .
,
test-utils.js
. , — . .
Sumário
. , , , , . , , . , : . . - , .
Caros leitores! React-?
