Le matériel, dont nous publions la traduction aujourd'hui, révèle les approches utilisées par son auteur lors de la structuration des applications React. En particulier, nous discuterons ici de la structure de dossiers utilisée, de la dénomination des entités, des endroits où se trouvent les fichiers de test et d'autres choses similaires.
L'une des caractéristiques les plus agréables de React est que cette bibliothèque n'oblige pas le développeur à respecter strictement certaines conventions concernant la structure du projet. Une grande partie de cela reste à la discrétion du programmeur. Cette approche est différente de celle, disons, adoptée dans les cadres Ember.js ou Angular. Ils offrent aux développeurs plus de fonctionnalités standard. Ces cadres prévoient des conventions concernant la structure des projets et des règles pour nommer les fichiers et les composants.

Personnellement, j'aime l'approche adoptée par React. Le fait est que je préfère contrôler quelque chose moi-même, sans compter sur certains «accords». Cependant, l'approche de structuration des projets qu'Angular offre de nombreux avantages. Le choix entre la liberté et des règles plus ou moins rigides se résume à ce qui est le plus proche de vous et de votre équipe.
Au fil des années de travail avec React, j'ai essayé de nombreuses façons de structurer les applications. Certaines des idées que j'ai appliquées se sont avérées plus efficaces que d'autres. Par conséquent, ici, je vais parler de tout ce qui s'est bien montré dans la pratique. J'espère que vous trouverez ici quelque chose qui vous sera utile.
Je n’essaie pas de montrer ici une façon «juste» de structurer les applications. Vous pouvez reprendre certaines de mes idées et les modifier pour répondre à vos besoins. Vous pourriez bien être en désaccord avec moi en continuant à travailler comme vous le faisiez auparavant. Différentes équipes créent différentes applications et utilisent différents moyens pour atteindre leurs objectifs.
Il est important de noter que si vous regardez le site Web
Thread , dont je participe au développement, et regardez le dispositif de son interface, vous trouverez des endroits où ces règles dont je parlerai ne sont pas respectées. Le fait est que toute «règle» de programmation ne doit être considérée que comme des recommandations et non comme des normes complètes valables dans toutes les situations. Et si vous pensez qu'une sorte de "règles" ne vous convient pas - vous, pour améliorer la qualité de ce sur quoi vous travaillez, devriez trouver la force de s'écarter de ces "règles".
En fait, maintenant, sans plus tarder, je vous propose mon histoire sur la structuration des applications React.
Ne vous inquiétez pas trop des règles.
Peut-être décidez-vous que la recommandation de ne pas trop vous soucier des règles semble étrange au début de notre conversation. Mais c'est exactement ce que je veux dire quand je dis que la principale erreur que les programmeurs ont en termes d'observation des règles est que les programmeurs attachent trop d'importance aux règles. Cela est particulièrement vrai au début des travaux sur un nouveau projet. Au moment de la création du premier
index.jsx
tout simplement impossible de savoir ce qui est le mieux pour ce projet. Au fur et à mesure que le projet se développe, vous arriverez naturellement à une sorte de structure de fichiers et de dossiers, ce qui est probablement très bon pour ce projet. Si, au cours de la poursuite des travaux, il s'avère que la structure existante est quelque peu infructueuse, elle peut être améliorée.
Si vous lisez ceci et vous surprenez à penser qu'il n'y a rien dans votre application qui est en cours de discussion, alors ce n'est pas un problème. Chaque application est unique, il n'y a pas deux équipes de développement absolument identiques. Par conséquent, chaque équipe, travaillant sur un projet, parvient à des accords concernant sa structure et ses méthodes de travail. Cela aide les membres de l'équipe à travailler de manière productive. Ne vous efforcez pas de, après avoir appris comment quelqu'un fait quelque chose, présentez-vous immédiatement cela. N'essayez pas d'introduire dans votre travail ce que l'on appelle dans certains matériaux, et même dans ce cas, le "moyen le plus efficace" de résoudre un problème. J'ai toujours adhéré et adhéré à la stratégie suivante concernant ces recommandations. J'ai mon propre ensemble de règles, mais en lisant comment les autres agissent dans certaines situations, je choisis ce qui me semble réussi et qui me convient. Cela conduit au fait qu'au fil du temps, mes méthodes de travail s'améliorent. En même temps, je n'ai pas de chocs et il n'y a aucune envie de tout réécrire à partir de zéro.
Les composants importants se trouvent dans des dossiers séparés
L'approche pour placer des fichiers de composants dans des dossiers auxquels je suis arrivé est que les composants qui peuvent être considérés comme «importants», «de base», «de base» dans le contexte de l'application sont placés dans des dossiers séparés. Ces dossiers se trouvent à leur tour dans le dossier des
components
. Par exemple, si nous parlons d'une application pour une boutique électronique, le composant
<Product>
utilisé pour décrire le produit peut être reconnu comme un composant similaire. Voici ce que je veux dire:
- src/ - components/ - product/ - product.jsx - product-price.jsx - navigation/ - navigation.jsx - checkout-flow/ - checkout-flow.jsx
Dans ce cas, les composants "secondaires" qui ne sont utilisés que par certains composants "principaux" se trouvent dans le même dossier que ces composants "principaux". Cette approche a fait ses preuves dans la pratique. Le fait est qu'en raison de son application, une certaine structure apparaît dans le projet, mais le niveau d'imbrication des dossiers n'est pas trop important. Son application n'entraîne pas l'apparition de quelque chose comme
../../../
dans les commandes d'importation de composants, elle ne rend pas difficile le déplacement dans le projet. Cette approche vous permet de créer une hiérarchie claire de composants. Ce composant, dont le nom correspond au nom du dossier, est considéré comme "de base". D'autres composants situés dans le même dossier servent à diviser le composant "de base" en parties, ce qui simplifie le travail avec le code de ce composant et son support.
Bien que je soutienne la présence d'une certaine structure de dossiers dans le projet, je crois que la chose la plus importante est la sélection de bons noms de fichiers. Les dossiers eux-mêmes sont moins importants.
Utilisation de sous-dossiers pour les sous-composants
L'un des inconvénients de l'approche ci-dessus est que son utilisation peut conduire à l'apparition de dossiers de composants «basiques» contenant de nombreux fichiers. Prenons, par exemple, le composant
<Product>
. Des fichiers CSS y seront attachés (nous en parlerons plus tard), des fichiers de test, de nombreux sous-composants et, éventuellement, d'autres ressources - telles que des images et des icônes SVG. Cette liste "d'ajouts" n'est pas limitée. Tout cela se retrouvera dans le même dossier que le composant "base".
Je m'en fiche vraiment. Cela me convient si les fichiers ont des noms bien pensés et s'ils peuvent être facilement trouvés (en utilisant les outils de recherche de fichiers dans l'éditeur). Si tel est le cas, la structure des dossiers s'estompe en arrière-plan.
Voici un tweet sur ce sujet.
Cependant, si vous préférez que votre projet ait une structure plus étendue, il n'y a rien de difficile à déplacer des sous-composants vers leurs propres dossiers:
- src/ - components/ - product/ - product.jsx - ... - product-price/ - product-price.jsx
Les fichiers de test se trouvent au même endroit que les fichiers des composants testés.
Nous commençons cette section par une simple recommandation, à savoir que les fichiers de test doivent être placés au même endroit que les fichiers avec le code qu'ils sont vérifiés avec leur aide. Je parlerai également de la façon dont je préfère structurer les composants, en essayant de veiller à ce qu'ils soient proches les uns des autres. Mais maintenant, je peux dire que je trouve pratique de placer les fichiers de test dans les mêmes dossiers que les fichiers de composants. Dans ce cas, les noms des fichiers avec les tests sont identiques aux noms des fichiers avec le code. Aux noms de test, avant l'extension du nom de fichier, le suffixe
.test
ajouté que:
- Nom du fichier du composant:
auth.js
- Nom du fichier de test:
auth.test.js
Cette approche a plusieurs atouts:
- Il facilite la recherche de fichiers de test. En un coup d'œil, vous pouvez comprendre s'il existe un test pour le composant avec lequel je travaille.
- Toutes les commandes d'importation nécessaires sont très simples. Dans le test, pour importer le code testé, vous n'avez pas besoin de créer des structures qui décrivent, par exemple, la sortie du dossier
__tests__
. Ces équipes semblent extrêmement simples. Par exemple, comme ceci: import Auth from './auth'
.
Si nous avons des données utilisées pendant le test, par exemple quelque chose comme des simulations de demande d'API, nous les mettons dans le même dossier où se trouvent déjà le composant et son test. Lorsque tout ce qui peut être nécessaire se trouve dans un seul dossier, cela contribue à la croissance de la productivité. Par exemple, si vous utilisez une structure de dossiers ramifiés et que le programmeur est sûr qu'un certain fichier existe, mais ne se souvient pas de son nom, le programmeur devra rechercher ce fichier dans de nombreux sous-répertoires. Avec l'approche proposée, il suffit de regarder le contenu d'un dossier et tout deviendra clair.
Modules CSS
Je suis un grand fan des
modules CSS . Nous avons constaté qu'ils sont parfaits pour créer des règles CSS modulaires pour les composants.
De plus, j'aime beaucoup la technologie des
composants de style . Cependant, au cours de travaux sur des projets auxquels de nombreux développeurs ont participé, il s'est avéré que la présence de vrais fichiers CSS dans le projet augmentait l'utilisabilité.
Comme vous l'avez probablement déjà deviné, nos fichiers CSS sont situés, comme les autres fichiers, à côté des fichiers de composants, dans les mêmes dossiers. Cela simplifie considérablement le mouvement entre les fichiers lorsque vous avez besoin de comprendre rapidement la signification d'une classe.
Une recommandation plus générale, dont l'essence imprègne tout ce matériel, est que tout le code lié à un certain composant doit être conservé dans le même dossier dans lequel se trouve ce composant. Il est révolu le temps où des dossiers distincts étaient utilisés pour stocker le code CSS et JS, le code de test et d'autres ressources. L'utilisation de structures de dossiers complexes complique le mouvement entre les fichiers et n'a aucun avantage évident, sauf qu'il aide à "organiser le code". Conservez les fichiers interconnectés dans le même dossier - cela signifie passer moins de temps à vous déplacer entre les dossiers pendant le travail.
Nous avons même créé un chargeur Webpack pour CSS, dont les capacités correspondent aux fonctionnalités de notre travail. Il vérifie les noms de classe déclarés et renvoie une erreur dans la console si nous nous référons à une classe qui n'existe pas.
Presque toujours, un seul code de composant est placé dans un fichier
Mon expérience montre que les programmeurs adhèrent généralement trop strictement à la règle selon laquelle le code d'un et d'un seul composant React doit être dans un seul fichier. En même temps, je soutiens pleinement l'idée qu'il ne vaut pas la peine de placer trop de composants dans un seul fichier (imaginez les difficultés de nommer de tels fichiers!). Mais je crois qu'il n'y a rien de mal à mettre dans le même fichier dans lequel se trouve le code d'un certain "gros" composant, et le code du "petit" composant qui lui est associé. Si un tel mouvement aide à préserver la pureté du code, si le "petit" composant n'est pas trop grand pour le mettre dans un fichier séparé, cela ne nuira à personne.
Par exemple, si je crée un composant
<Product>
, et j'ai besoin d'un petit morceau de code pour afficher le prix, alors je peux le faire:
const Price = ({ price, currency }) => ( <span> {currency} {formatPrice(price)} </span> ) const Product = props => {
La bonne chose à propos de cette approche est que je n'ai pas eu à créer de fichier séparé pour le composant
<Price>
et que ce composant est disponible exclusivement pour le composant
<Product>
. Nous n'exportons pas ce composant, il ne peut donc pas être importé ailleurs dans l'application. Cela signifie que lorsqu'on vous demande de mettre
<Price>
dans un fichier séparé, vous pouvez donner une réponse claire et positive si vous devez l'importer ailleurs. Sinon, vous pouvez vous passer de mettre le code
<Price>
dans un fichier séparé.
Dossiers séparés pour les composants universels
Nous utilisons récemment des composants universels. En fait, ils forment notre système de conception (que nous prévoyons de publier un jour), mais jusqu'à présent, nous avons commencé petit - avec des composants comme
<Button>
et
<Logo>
. Un composant est considéré comme «universel» s'il n'est pas lié à une partie spécifique du site, mais est l'un des éléments constitutifs de l'interface utilisateur.
Des composants similaires se trouvent dans votre propre dossier (
src/components/generic
). Cela simplifie considérablement le travail avec tous les composants universels. Ils sont au même endroit - c'est très pratique. Au fil du temps, au fur et à mesure que le projet se développe, nous prévoyons de développer un guide de style (nous sommes de grands fans de
react-styleguidist ) afin de simplifier davantage le travail avec des composants universels.
Utilisation d'alias pour importer des entités
La structure de dossiers relativement plate dans nos projets garantit que les commandes d'importation n'ont pas de structures trop longues comme
../../
. Mais c'est difficile de s'en passer. Par conséquent, nous avons utilisé
babel-plugin-module-resolver pour configurer des alias qui simplifient les commandes d'importation.
Vous pouvez faire de même avec Webpack, mais grâce au plugin Babel, les mêmes commandes d'importation peuvent fonctionner dans les tests.
Nous l'avons configuré avec une paire d'alias:
{ components: './src/components', '^generic/([\\w_]+)': './src/components/generic/\\1/\\1', }
Le premier est extrêmement simple. Il vous permet d'importer n'importe quel composant, en démarrant la commande avec le mot
components
. Dans l'approche normale, les commandes d'importation ressemblent à ceci:
import Product from '../../components/product/product'
Au lieu de cela, nous pouvons les écrire comme ceci:
import Product from 'components/product/product'
Les deux commandes importent le même fichier. C'est très pratique, car cela vous permet de ne pas penser à la structure des dossiers.
Le deuxième alias est un peu plus compliqué:
'^generic/([\\w_]+)': './src/components/generic/\\1/\\1',
Nous utilisons ici l'expression régulière. Il trouve les commandes d'importation qui commencent par
generic
(le signe
^
au début de l'expression vous permet de sélectionner uniquement les commandes qui commencent par
generic
) et capture ce qui est après
generic/
dans le groupe. Après cela, nous utilisons le fragment capturé (
\\1
) dans la construction
./src/components/generic/\\1/\\1
.
Par conséquent, nous pouvons utiliser les commandes d'importation pour les composants universels de ce type:
import Button from 'generic/button'
Ils sont convertis en commandes suivantes:
import Button from 'src/components/generic/button/button'
Cette commande, par exemple, sert à importer un fichier JSX décrivant un bouton universel. Nous avons fait tout cela parce que cette approche simplifie considérablement l'importation de composants universels. De plus, cela nous sera très utile si nous décidons de changer la structure des fichiers du projet (cela, comme notre système de conception se développe, est tout à fait possible).
Ici, je voudrais noter que vous devez être prudent lorsque vous travaillez avec des pseudonymes. Si vous n'en avez que quelques-uns et qu'ils sont conçus pour résoudre des problèmes d'importation standard, tout va bien. Mais si vous en avez beaucoup, ils peuvent apporter plus de confusion que de bien.
Dossier lib universel pour les utilitaires
Je voudrais retrouver tout le temps que j'ai passé à essayer de trouver l'endroit parfait pour du code qui n'est pas du code composant. J'ai partagé tout cela selon différents principes, en mettant en évidence le code des utilitaires, des services, des fonctions auxiliaires. Tout cela a tellement de noms que je ne les mentionnerai pas tous. Maintenant, je n'essaie pas de comprendre la différence entre "l'utilitaire" et la "fonction auxiliaire" afin de trouver la bonne place pour un certain fichier. Maintenant, j'utilise une approche beaucoup plus simple et plus compréhensible: tout cela tombe dans un seul dossier
lib
.
À long terme, la taille de ce dossier peut s'avérer si grande que vous devez le structurer d'une manière ou d'une autre, mais c'est tout à fait normal. Il est toujours plus facile d'équiper quelque chose d'une certaine structure que de se débarrasser des erreurs de structuration excessive.
Dans notre projet Thread, le dossier
lib
contient environ 100 fichiers. Ils sont divisés à peu près également en fichiers contenant l'implémentation de certaines fonctionnalités et en fichiers de test. Cela n'a pas posé de difficultés pour trouver les fichiers nécessaires. Grâce aux
lib/name_of_thing
recherche intelligents intégrés à la plupart des éditeurs, je dois presque toujours saisir quelque chose comme
lib/name_of_thing
, et ce dont j'ai besoin se trouve.
De plus, nous avons un alias qui simplifie l'importation à partir du dossier
lib
, vous permettant d'utiliser des commandes de ce type:
import formatPrice from 'lib/format_price'
Ne soyez pas alarmé par les structures de dossiers plats qui peuvent entraîner le stockage de plusieurs fichiers dans un dossier. Habituellement, une telle structure est tout ce qui est nécessaire pour un certain projet.
Masquage de bibliothèques tierces derrière des API natives
J'aime vraiment le
système de surveillance des bogues
Sentry . Je l'ai souvent utilisé lors du développement de parties serveur et client d'applications. Avec son aide, vous pouvez intercepter des exceptions et recevoir des notifications sur leur occurrence. Il s'agit d'un excellent outil qui nous permet de nous tenir au courant des problèmes rencontrés sur le site.
Chaque fois que j'utilise une bibliothèque tierce dans mon projet, je réfléchis à la manière de la faire pour que, si nécessaire, elle puisse être remplacée le plus facilement possible par autre chose. Souvent, comme avec le même système Sentry que nous aimons vraiment, ce n'est pas nécessaire. Mais, juste au cas où, cela ne fait jamais de mal de penser à un moyen d'éviter d'utiliser un certain service ou un moyen de le changer pour autre chose.
La meilleure solution à ce problème est de développer votre propre API qui cache les outils des autres. Cela ressemble à la création d'un module
lib/error-reporting.js
qui exporte la fonction
reportError()
. Le cœur de ce module utilise Sentry. Mais Sentry n'est directement importé que dans ce module et nulle part ailleurs. Cela signifie que remplacer Sentry par un autre outil sera très simple. Pour ce faire, il suffira de changer un fichier en un seul endroit. Tant que l'API publique de ce fichier reste inchangée, le reste du projet ne saura même pas qu'en appelant
reportError()
, ce n'est pas Sentry qui est utilisé, mais autre chose.
Veuillez noter que l'API publique du module s'appelle les fonctions qu'elle exporte et leurs arguments. Ils sont également appelés l'interface publique du module.
Utilisation de PropTypes (ou d'outils tels que TypeScript ou Flow)
Quand je fais de la programmation, je pense à trois versions de moi-même:
- Jack du passé et le code qu'il a écrit (code parfois douteux).
- Aujourd'hui, Jack et le code qu'il écrit maintenant.
- Jack du futur. Quand je pense à cet avenir moi-même, je me pose la question de savoir comment écrire du code qui me facilitera la vie à l'avenir.
Cela peut sembler bizarre, mais je l'ai trouvé utile, en réfléchissant à la façon d'écrire du code, posez la question suivante: "Comment sera-t-il perçu dans six mois?".
Un moyen simple de vous rendre présent et de vous rendre plus productif consiste à spécifier les types de propriétés (
PropTypes
) utilisées par les composants. Cela vous fera gagner du temps dans la recherche de fautes de frappe possibles. Cela vous protégera des situations où, en utilisant le composant, des propriétés de mauvais types sont appliquées, ou ils oublient complètement le transfert de propriétés. Dans notre cas, la
règle eslint-react / prop-types est un bon rappel de la nécessité d'utiliser
PropTypes
.
Si vous allez encore plus loin, il est recommandé de décrire les propriétés aussi précisément que possible. Par exemple, vous pouvez faire ceci:
blogPost: PropTypes.object.isRequired
Mais ce serait bien mieux de faire ça:
blogPost: PropTypes.shape({ id: PropTypes.number.isRequired, title: PropTypes.string.isRequired,
Dans le premier exemple, la vérification minimale nécessaire est effectuée. Dans le second, le développeur reçoit des informations beaucoup plus utiles. Ils seront très utiles, par exemple, si quelqu'un oublie un certain champ utilisé dans l'objet.
Les bibliothèques tierces ne sont utilisées que lorsqu'elles sont vraiment nécessaires.
Cette astuce est plus pertinente que jamais avec l'avènement des
hameçons React . Par exemple, j'ai participé à une importante modification d'une des parties du site Thread et j'ai décidé d'accorder une attention particulière à l'utilisation de bibliothèques tierces. , , . ( ), .
, 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
. , — . .
Résumé
. , , , , . , , . , : . . - , .
Chers lecteurs! React-?
