La complexité des sites Web modernes a considérablement augmenté au cours des dernières années. La demande croissante de conceptions de haute qualité et conformes aux normes de l'industrie intensifie encore les défis auxquels sont confrontés les développeurs frontaux.
Aujourd'hui, même les applications frontales ont besoin de considérations architecturales pour rationaliser le processus de développement. Dans mon article précédent , j'ai partagé mon expérience de mise en œuvre de l'approche d'architecture propre dans les applications frontales tout en travaillant sur mon projet parallèle .
Dans cet article, je vise à approfondir l'approche de conception atomique, en m'inspirant de mes expériences avec le même projet. Je discuterai de ses avantages et de ses inconvénients et évaluerai son utilité dans différents scénarios.
Pour commencer, explorons le concept d'un système de conception. Les systèmes de conception sont des collections complètes de composants, de directives et de principes réutilisables qui permettent aux équipes de concevoir et de développer des interfaces utilisateur cohérentes sur plusieurs plates-formes.
Ils agissent comme une source unique de vérité pour les concepteurs et les développeurs, garantissant que les aspects visuels et fonctionnels d'un produit s'alignent et adhèrent à l'identité de marque établie. Si vous souhaitez explorer des exemples d'implémentations de systèmes de conception, envisagez d'examiner les éléments suivants :
Si vous souhaitez approfondir le sujet des systèmes de conception, je vous recommande de consulter cet article . Il décrit ce sujet en détail, détails qui ne nous sont pas nécessaires dans le cadre de ce travail.
S'appuyant sur les bases des systèmes de conception, la conception atomique est une méthodologie qui rationalise l'organisation et la structuration des composants et des directives réutilisables. Conçu par Brad Frost, Atomic Design s'inspire de la chimie, car il déconstruit les interfaces utilisateur dans leurs blocs de construction les plus fondamentaux et les réassemble dans des structures plus complexes.
Voici une image illustrant l'analogie avec la chimie :
Les réactions chimiques sont représentées par des équations chimiques, qui montrent souvent comment les éléments atomiques se combinent pour former des molécules. Dans l'exemple ci-dessus, nous voyons comment l'hydrogène et l'oxygène se combinent pour former des molécules d'eau.
En substance, la conception atomique est une évolution naturelle des systèmes de conception, offrant une approche systématique de la création de composants flexibles et évolutifs. En appliquant les principes de la conception atomique, les équipes peuvent gérer leurs systèmes de conception plus efficacement, car la nature modulaire de cette méthodologie facilite la maintenance, la mise à jour et l'extension des composants et des modèles au sein du système.
Si vous craignez que cela puisse sembler complexe, ne vous inquiétez pas. Dans les sections à venir, je montrerai comment appliquer ces principes à l'aide d'un exemple concret de l'application que j'ai développée, ce qui facilitera sa compréhension et sa mise en œuvre dans vos propres projets.
La conception atomique organise les composants en cinq niveaux distincts, chacun s'appuyant sur le précédent. Explorons ces cinq niveaux en détail :
atomes : les blocs de construction les plus élémentaires d'une interface utilisateur, les atomes représentent des éléments HTML individuels tels que des boutons, des champs de saisie et des en-têtes. Ce sont les plus petites unités fonctionnelles et ne peuvent pas être décomposées davantage.
molécules : les molécules sont formées en combinant deux atomes ou plus en un groupe fonctionnel. Par exemple, une molécule de formulaire de recherche peut être constituée d'un atome d'entrée de recherche, d'un atome de bouton et d'un atome d'étiquette. Les molécules représentent des composants simples qui peuvent être réutilisés dans un projet.
organismes : les organismes sont des composants plus complexes, créés en combinant plusieurs molécules et/ou atomes. Ils représentent des sections distinctes d'une interface utilisateur, telles qu'un en-tête, un pied de page ou une barre latérale. Les organismes aident à former la mise en page et la structure globales d'une page.
modèles : les modèles sont essentiellement des mises en page construites à l'aide d'organismes, de molécules et d'atomes. Ils définissent la structure et la disposition des composants sur une page sans spécifier de contenu réel, servant de modèle pour divers scénarios de contenu.
pages : les pages sont les instances finales et entièrement réalisées des modèles, complètes avec du contenu et des données réels. Ils représentent ce que les utilisateurs verront et interagiront finalement, montrant comment les composants et la mise en page s'adaptent aux différents types de contenu et cas d'utilisation.
Afin de développer une perspective bien informée sur la conception atomique pour le développement frontend, je me suis lancé dans un voyage pour créer une application. Sur une période de six mois, j'ai acquis des connaissances et une expérience précieuses tout en travaillant sur ce projet.
Par conséquent, les exemples fournis tout au long de cet article s'appuient sur mon expérience pratique avec l'application. Pour maintenir la transparence, tous les exemples sont dérivés d'un code accessible au public.
Vous pouvez explorer le résultat final en visitant le référentiel ou le site Web lui-même.
Gardez à l'esprit que j'utiliserai des exemples codés dans React . Si vous n'êtes pas familier avec ce langage, ne vous inquiétez pas - j'ai cherché à illustrer les concepts fondamentaux de la conception atomique, plutôt que de me concentrer sur les détails du code.
Pour mieux comprendre les composants de mon référentiel, vous pouvez les trouver sous le répertoire suivant : /client/presentation
. À cet endroit, j'ai créé un nouveau répertoire appelé atoms
pour maintenir une dénomination cohérente avec la méthodologie de conception atomique. Ce nouveau répertoire contient toutes les petites pièces nécessaires pour construire l'ensemble du processus d'intégration.
La liste complète des atomes est la suivante :
atoms ├── box ├── button ├── card ├── card-body ├── card-footer ├── container ├── divider ├── flex ├── form-control ├── form-error-message ├── form-helper-text ├── form-label ├── heading ├── icon ├── input ├── list ├── list-icon ├── list-item ├── spinner ├── tab ├── tab-list ├── tab-panel ├── tab-panels ├── tabs └── text
Ces noms d'atomes vous sont peut-être familiers car ils sont basés sur le package Chakra UI . La plupart d'entre eux contiennent déjà le style de correspondance par défaut pour mon application, il n'y a donc rien de particulièrement unique à décrire à ce niveau. Dans cet esprit, nous pouvons passer directement à la discussion des molecules
.
À ce stade, le processus de conception atomique devient plus intéressant et sa véritable puissance commence à se révéler. Bien que la définition de vos atomes de base ait été une tâche longue et monotone, la construction de nouveaux composants à l'aide d'atomes devient beaucoup plus agréable.
Pour définir les molécules, j'ai créé un répertoire molecules
dans mon répertoire /client/presentation
. La liste complète des molécules requises est la suivante :
molecules ├── available-notion-database ├── full-screen-loader ├── input-control ├── onboarding-step-layout └── onboarding-tab-list
En effet, avec seulement cinq molécules, nous avons suffisamment de composants pour atteindre notre objectif. Il est important de noter qu'il s'agit également d'un endroit idéal pour inclure des mises en page partagées construites sur d'autres atomes. Par exemple, la onboarding-step-layout
est utilisée pour maintenir une apparence cohérente tout au long des cinq étapes du processus d'intégration.
Les autres composants sont les suivants :
available-notion-database : utilisé pour afficher les détails de la base de données de l'utilisateur récupéré (les utilisateurs peuvent avoir plusieurs bases de données, je propose donc la possibilité d'en choisir une à l'étape 4).
Le composant apparaît sur l'interface utilisateur comme ceci :
import { FC } from 'react'; import { Flex, Spinner } from '@presentation/atoms'; import { FullScreenLoaderProps } from './full-screen-loader.types'; export const FullScreenLoader: FC<FullScreenLoaderProps> = ({ children, ...restProps }): JSX.Element => ( <Flex alignItems="center" bg="gray.50" height="full" justifyContent="center" left={0} position="fixed" top={0} width="full" zIndex="9999" {...restProps} > <Spinner /> {children} </Flex> );
Il n'y a pas de science-fusée ici. Il s'agit simplement d'une combinaison des atomes flex
et spinner
déjà définis.
input
avec form-label
, form-control
, form-error-label
et spinner
pour montrer s'il y a une action en arrière-plan. Le composant apparaît sur l'interface utilisateur comme ceci :
Maintenant que plus de pièces sont prêtes, nous pouvons passer à la définition de blocs plus grands dans notre puzzle de conception.
Cette section est l'endroit où je crée chaque composant responsable de l'affichage de chaque étape du processus d'intégration.
Pour clarifier les choses, je vais juste vous montrer la liste des organismes créés :
organisms ├── onboarding-step-one ├── onboarding-step-two ├── onboarding-step-three ├── onboarding-step-four └── onboarding-step-five
Je crois que les noms sont explicites et qu'il ne devrait y avoir aucun malentendu. Pour illustrer comment j'ai tout assemblé, je vais présenter le code d'une étape à titre d'exemple. Bien sûr, si vous voulez en savoir plus, visitez simplement mon référentiel .
export const OnboardingStepFour: FC<OnboardingStepFourProps> = ({ onBackButtonClick, onNextButtonClick, }): JSX.Element => { const { hasApiTokenData, isSetApiTokenLoading, setApiToken, setApiTokenError } = useSetApiToken(); const handleInputChange = debounce(async (event: ChangeEvent<HTMLInputElement>) => { const result = await setApiToken(event.target.value); if (result) { onNextButtonClick(); } }, 1000); return ( <OnboardingStepLayout subtitle="Paste your copied integration token below to validate your integration." title="Validate your integration" onBackButtonClick={onBackButtonClick} > <InputControl isRequired errorMessage={setApiTokenError || undefined} isDisabled={isSetApiTokenLoading || hasApiTokenData} isLoading={isSetApiTokenLoading} label="Integration token" name="integrationToken" placeholder="Your integration token" onChange={handleInputChange} /> </OnboardingStepLayout> ); };
Ce code est entièrement responsable de l'affichage de la quatrième étape de mon processus d'intégration. Je crois que la seule préoccupation que vous pourriez avoir est de faire des requêtes dans des organismes. Est-ce acceptable ? Il n'y a pas de réponse unique et je dois répondre à ces préoccupations par "Cela dépend". Cela dépend de votre structure.
Si inclure un appel API dans une molécule ou un organisme a du sens dans le contexte de votre application et ne complique pas trop le composant, cela peut être une solution acceptable. Veillez simplement à ne pas laisser les composants de présentation devenir trop étroitement liés à la récupération de données ou à la logique métier, car cela peut les rendre plus difficiles à maintenir et à tester.
Dans mon scénario, ce composant est utilisé à un seul endroit, et d'autres solutions pour effectuer un appel d'API dans ce scénario sont plus complexes et peuvent produire beaucoup plus de code que nécessaire.
À ce stade, l'accent est mis sur la structure et la disposition des composants plutôt que sur les détails les plus fins de l'interface utilisateur. Les modèles aident également à identifier où la gestion de l'état doit résider, qui se trouve généralement dans les composants de page qui utilisent les modèles.
Dans l'exemple de code fourni, nous avons un composant Onboarding
qui sert de modèle :
import { FC } from 'react'; import { Flex, Heading, TabPanels, Tabs, Text } from '@presentation/atoms'; import { OnboardingTabList } from '@presentation/molecules'; import { OnboardingStepFive, OnboardingStepFour, OnboardingStepOne, OnboardingStepThree, OnboardingStepTwo, } from '@presentation/organisms'; import { OnboardingProps } from './onboarding.types'; export const Onboarding: FC<OnboardingProps> = ({ activeTabs, createNotionIntegrationTabRef, displayCreateNotionIntegrationTab, displaySelectNotionDatabaseTab, displayShareDatabaseIntegrationTab, displayValidateIntegrationTab, displayVerifyDatabaseTab, selectNotionDatabaseTabRef, shareDatabaseIntegrationTabRef, validateIntegrationTabRef, verifyDatabaseTabRef, }) => ( <Flex direction="column" overflowX="hidden" px={2} py={{ base: '20px', sm: '25px', md: '55px' }}> <Flex direction="column" textAlign="center"> <Heading color="gray.700" fontSize={{ base: 'xl', sm: '2xl', md: '3xl', lg: '4xl' }} fontWeight="bold" mb="8px" > Configure your Notion integration </Heading> <Text withBalancer color="gray.400" fontWeight="normal"> This information will let us know from which Notion database we should use to get your vocabulary. </Text> </Flex> <Tabs isLazy display="flex" flexDirection="column" mt={{ base: '10px', sm: '25px', md: '35px' }} variant="unstyled" > <OnboardingTabList activeTabs={activeTabs} createNotionIntegrationTabRef={createNotionIntegrationTabRef} selectNotionDatabaseTabRef={selectNotionDatabaseTabRef} shareDatabaseIntegrationTabRef={shareDatabaseIntegrationTabRef} validateIntegrationTabRef={validateIntegrationTabRef} verifyDatabaseTabRef={verifyDatabaseTabRef} /> <TabPanels maxW={{ md: '90%', lg: '100%' }} mt={{ base: '10px', md: '24px' }} mx="auto"> <OnboardingStepOne onNextButtonClick={displayCreateNotionIntegrationTab} /> <OnboardingStepTwo onBackButtonClick={displayVerifyDatabaseTab} onNextButtonClick={displayShareDatabaseIntegrationTab} /> <OnboardingStepThree onBackButtonClick={displayCreateNotionIntegrationTab} onNextButtonClick={displayValidateIntegrationTab} /> {activeTabs.validateIntegration ? ( <OnboardingStepFour onBackButtonClick={displayShareDatabaseIntegrationTab} onNextButtonClick={displaySelectNotionDatabaseTab} /> ) : null} {activeTabs.selectNotionDatabase ? ( <OnboardingStepFive onBackButtonClick={displayVerifyDatabaseTab} /> ) : null} </TabPanels> </Tabs> </Flex> );
Ce composant Onboarding
assemble des atomes, des molécules et des organismes pour créer la disposition du processus d'intégration. Notez que la logique de gestion d'état et de navigation par onglet a été séparée de ce composant. Les fonctions d'état et de rappel nécessaires sont désormais reçues en tant qu'accessoires, permettant à un composant "page" de niveau supérieur de gérer l'état et la gestion des données.
Cette séparation des préoccupations maintient le modèle axé sur la mise en page et la structure tout en garantissant que la gestion de l'état est gérée au niveau approprié.
À la fin, je voudrais présenter l'étape 4 comme résultat final :
Dans le contexte de notre discussion précédente, le composant "page" utilise le modèle Onboarding
et gère la gestion de l'état du processus d'intégration. Bien que le code de ce composant de page spécifique ne soit pas fourni ici, vous pouvez le trouver dans mon référentiel. Comme mentionné, il n'y a rien d'extraordinaire dans le code du composant de page ; il se concentre principalement sur la gestion de l'état et sa transmission au modèle Onboarding
.
Si nous regardons à quoi ressemble la conception atomique dans la pratique. Plongeons-nous dans les avantages et les inconvénients de cette approche.
Bien que la conception atomique offre de nombreux avantages évidents, tels que la modularité, la réutilisabilité et la maintenabilité, elle présente également quelques inconvénients qui méritent d'être pris en compte au début :
configuration initiale et complexité : la conception atomique nécessite une structure et une organisation bien planifiées, ce qui peut prendre du temps et être difficile à mettre en place initialement. Cela peut également introduire une complexité supplémentaire dans votre base de code, en particulier pour les petits projets où une telle approche granulaire peut être inutile.
courbe d'apprentissage : pour les développeurs débutants dans la conception atomique, la méthodologie peut avoir une courbe d'apprentissage abrupte. Cela nécessite une solide compréhension des différents niveaux et de leur articulation, ce qui peut être écrasant pour les débutants.
frais généraux : la mise en œuvre de la conception atomique peut impliquer la création d'un grand nombre de petits composants spécialisés. Cela peut entraîner une augmentation des frais généraux liés à la gestion et à la maintenance de ces composants, en particulier lorsqu'un composant n'est utilisé que dans un contexte spécifique.
risque de sur-ingénierie : en mettant l'accent sur la création de composants réutilisables et modulaires, il existe un risque potentiel de sur-ingénierie, où les développeurs peuvent passer trop de temps à affiner les composants individuels au lieu de se concentrer sur l'application plus large.
communication et collaboration : le succès de la conception atomique dépend d'une communication et d'une collaboration claires entre les concepteurs, les développeurs et les autres parties prenantes. Le fait de ne pas établir un langage commun ou une compréhension de la méthodologie peut entraîner de la confusion et des incohérences dans la mise en œuvre.
Cependant, cette approche a ses propres forces déjà mentionnées. Parlons-en plus en détail :
évolutivité : en décomposant la conception en éléments les plus fondamentaux, la construction de la complexité des composants est devenue une tâche plus gérable. Alors que la fabrication d'atomes posait des défis, la création de composants basés sur ces atomes était extrêmement agréable.
efficacité : la possibilité de réutiliser les atomes, les molécules et les organismes réduit considérablement le temps consacré à la conception et au développement de nouvelles fonctionnalités. Une fois les composants de base établis, créer de nouvelles interfaces peut être aussi simple que de combiner des éléments existants.
cohérence : découle directement du point précédent. Étant donné que les mêmes atomes, molécules et organismes sont utilisés dans plusieurs modèles et pages, l'interface utilisateur reste uniforme, offrant une expérience transparente aux utilisateurs.
documentation : la conception atomique supporte intrinsèquement la documentation. La structure à base d'atomes peut servir de guide visuel clair sur la façon dont les composants doivent être construits et utilisés. Cela peut être particulièrement utile pour l'intégration de nouveaux membres de l'équipe.
maintenabilité : l'une des plus grandes forces de la conception atomique est sa contribution à la maintenabilité d'un système de conception. En décomposant tout en ses parties atomiques, toute modification ou mise à jour peut être effectuée au niveau atomique, puis propagée dans le système. Par exemple, si vous décidez de changer la couleur d'un bouton, vous n'avez besoin d'effectuer ce changement qu'une seule fois au niveau de l'atome, et cela se reflétera sur toutes les molécules, organismes et modèles où ce bouton est utilisé. Cela simplifie grandement le processus de mise à jour et de maintenance du système de conception au fil du temps.
En conclusion, alors que la conception atomique peut sembler une épée à double tranchant - un peu intimidante en termes de configuration initiale et de courbe d'apprentissage - ses avantages potentiels valent bien la lutte initiale. Et rappelez-vous, même les épées les plus redoutables sont inoffensives entre les mains d'un chevalier habile !