Petits composants: qu'est-ce qui pourrait mal tourner? Nous utilisons le principe de l'entière responsabilité

Nous présentons à votre attention une traduction d'un article de Scott Domes, qui a été publié sur blog.bitsrc.io. Découvrez sous kat pourquoi les composants doivent être aussi petits que possible et comment le principe de la responsabilité exclusive affecte la qualité des applications.


Photo Austin Kirk avec Unsplash

L'avantage du système de composants React (et des bibliothèques similaires) est que votre interface utilisateur est divisée en petites parties faciles à lire et réutilisables.

Ces composants sont compacts (100-200 lignes), ce qui permet aux autres développeurs de les comprendre et de les modifier facilement.

Bien que les composants, en règle générale, essaient d'être plus courts, il n'y a pas de restriction claire et stricte sur leur longueur. React ne vous dérangera pas si vous décidez d'intégrer votre application dans un composant effroyablement énorme, composé de 3000 lignes.

... mais ça n'en vaut pas la peine. La plupart de vos composants, probablement, sont déjà trop volumineux - ou plutôt, ils remplissent trop de fonctions.

Dans cet article, je prouverai que la plupart des composants (même avec la longueur habituelle de 200 lignes) devraient être ciblés plus étroitement. Ils ne devraient remplir qu'une seule fonction et l'exécuter correctement. C'est ce qu'Eddie Osmani dit grand ici .

Astuce : lorsque vous travaillez dans JS, utilisez Bit pour organiser, assembler et réutiliser des composants en tant que pièces lego. Bit est un outil extrêmement efficace pour cette entreprise, il vous aidera, vous et votre équipe, à gagner du temps et à accélérer l'assemblage. Essayez-le.

Montrons comment, lors de la création de composants , quelque chose peut mal tourner .

Notre appli


Imaginez que nous ayons une application standard pour les blogueurs. Et voici ce que sur l'écran principal:

class Main extends React.Component { render() { return ( <div> <header> // Header JSX </header> <aside id="header"> // Sidebar JSX </aside> <div id="post-container"> {this.state.posts.map(post => { return ( <div className="post"> // Post JSX </div> ); })} </div> </div> ); } } 

(Cet exemple, comme de nombreux autres, doit être considéré comme un pseudo-code.)

Il affiche le panneau supérieur, la barre latérale et la liste des articles. Tout est simple.

Comme nous devons également télécharger des publications, nous pouvons le faire pendant le montage du composant:

 class Main extends React.Component { state = { posts: [] }; componentDidMount() { this.loadPosts(); } loadPosts() { // Load posts and save to state } render() { // Render code } } 

Nous avons également une certaine logique par laquelle la barre latérale est appelée. Si l'utilisateur clique sur le bouton dans le panneau supérieur, le côté sortira. Vous pouvez le fermer à partir du haut et du panneau latéral lui-même.

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false }; componentDidMount() { this.loadPosts(); } loadPosts() { // Load posts and save to state } handleOpenSidebar() { // Open sidebar by changing state } handleCloseSidebar() { // Close sidebar by changing state } render() { // Render code } } 

Notre composant est devenu un peu plus compliqué, mais toujours facile à lire.

On peut affirmer que toutes ses parties ont un seul objectif: afficher la page principale de l'application. Nous suivons donc le principe de la responsabilité exclusive.

Le principe de la responsabilité exclusive stipule qu'un composant ne doit remplir qu'une seule fonction. Si nous reformulons la définition tirée de wikipedia.org , il s'avère que chaque composant ne devrait être responsable que d'une partie de la fonctionnalité [application].

Notre composant principal répond à cette exigence. Quel est le problème?

Voici une formulation différente du principe: tout [composant] ne devrait avoir qu'une seule raison de changer .

Cette définition est tirée du livre de Robert Martin , Rapid Software Development. Principes, exemples, pratique » et c'est d'une grande importance.

En nous concentrant sur une raison de changer nos composants, nous pouvons créer de meilleures applications qui, de plus, seront faciles à configurer.

Pour plus de clarté, compliquons notre composant.

Complication


Supposons qu'un mois après la mise en œuvre du composant principal, une nouvelle fonctionnalité ait été attribuée au développeur de notre équipe. L'utilisateur pourra désormais masquer une publication (par exemple, si elle contient du contenu inapproprié).

Ce n'est pas difficile à faire!

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false, postsToHide: [] }; // older methods get filteredPosts() { // Return posts in state, without the postsToHide } render() { return ( <div> <header> // Header JSX </header> <aside id="header"> // Sidebar JSX </aside> <div id="post-container"> {this.filteredPosts.map(post => { return ( <div className="post"> // Post JSX </div> ); })} </div> </div> ); } } 

Notre collègue s'en est facilement occupé. Elle n'a ajouté qu'une nouvelle méthode et une nouvelle propriété. Aucun de ceux qui ont examiné la courte liste de changements n'a émis d'objection.

Quelques semaines plus tard, une autre fonctionnalité est annoncée - une barre latérale améliorée pour la version mobile. Au lieu de jouer avec CSS, le développeur décide de créer plusieurs composants JSX qui ne fonctionneront que sur les appareils mobiles.

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false, postsToHide: [], isMobileSidebarOpen: false }; // older methods handleOpenSidebar() { if (this.isMobile()) { this.openMobileSidebar(); } else { this.openSidebar(); } } openSidebar() { // Open regular sidebar } openMobileSidebar() { // Open mobile sidebar } isMobile() { // Check if mobile device } render() { // Render method } } 

Un autre petit changement. Quelques nouvelles méthodes bien nommées et une nouvelle propriété.

Et ici, nous avons un problème. Main n'exécute toujours qu'une seule fonction (rendre l'écran principal), mais vous regardez toutes ces méthodes que nous traitons maintenant:

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false, postsToHide: [], isMobileSidebarOpen: false }; componentDidMount() { this.loadPosts(); } loadPosts() { // Load posts and save to state } handleOpenSidebar() { // Check if mobile then open relevant sidebar } handleCloseSidebar() { // Close both sidebars } openSidebar() { // Open regular sidebar } openMobileSidebar() { // Open mobile sidebar } isMobile() { // Check if mobile device } get filteredPosts() { // Return posts in state, without the postsToHide } render() { // Render method } } 

Notre composant devient grand et volumineux, il est difficile à comprendre. Et avec l'extension des fonctionnalités, la situation ne fera qu'empirer.

Qu'est-ce qui a mal tourné?

Seule raison


Revenons à la définition du principe de la responsabilité exclusive: tout composant ne devrait avoir qu'une seule raison de changer .

Auparavant, nous avons changé la façon dont les publications sont affichées, nous avons donc dû changer notre composant principal. Ensuite, nous avons changé la façon dont la barre latérale s'ouvre - et encore une fois, nous avons changé le composant principal.

Cette composante a de nombreuses raisons indépendantes de changement. Cela signifie qu'il remplit trop de fonctions .

En d'autres termes, si vous pouvez modifier de manière significative une partie de votre composant et que cela n'entraîne pas de modifications dans une autre partie de celui-ci, alors votre composant a trop de responsabilités.

Séparation plus efficace


La solution est simple: vous devez diviser le composant principal en plusieurs parties. Comment faire

Commençons à nouveau. Le rendu de l'écran principal reste la responsabilité du composant principal, mais nous le réduisons uniquement pour afficher les composants associés:

 class Main extends React.Component { render() { return ( <Layout> <PostList /> </Layout> ); } } 

Super

Si nous changeons soudainement la disposition de l'écran principal (par exemple, ajoutons des sections supplémentaires), alors Main changera également. Dans d'autres cas, nous n'aurons aucune raison de le toucher. Super.

Passons à la Layout en Layout :

 class Layout extends React.Component { render() { return ( <SidebarDisplay> {(isSidebarOpen, toggleSidebar) => ( <div> <Header openSidebar={toggleSidebar} /> <Sidebar isOpen={isSidebarOpen} close={toggleSidebar} /> </div> )} </SidebarDisplay> ); } } 

C'est un peu plus compliqué. Layout est responsable du rendu des composants de mise en page (panneau latéral / panneau supérieur). Mais nous ne succomberons pas à la tentation et donnerons à la Layout responsabilité de déterminer si la barre latérale est ouverte ou non.

Nous SidebarDisplay cette fonction au composant SidebarDisplay , qui transmet les méthodes ou états nécessaires aux composants Header et Sidebar .

(L'exemple ci-dessus est un modèle Render Props via Children dans React. Si vous ne le connaissez pas, ne vous inquiétez pas. Il est important qu'il existe un composant distinct qui contrôle l'état ouvert / fermé de la barre latérale.)

Et puis, la Sidebar elle-même peut être assez simple si elle est uniquement responsable du rendu de la barre latérale à droite.

 class Sidebar extends React.Component { isMobile() { // Check if mobile } render() { if (this.isMobile()) { return <MobileSidebar />; } else { return <DesktopSidebar />; } } } 

Encore une fois, nous résistons à la tentation d'insérer JSX pour ordinateurs / appareils mobiles directement dans ce composant, car dans ce cas, il y aura deux raisons pour le changement.

Regardons un autre composant:

 class PostList extends React.Component { state = { postsToHide: [] } filterPosts(posts) { // Show posts, minus hidden ones } hidePost(post) { // Save hidden post to state } render() { return ( <PostLoader> { posts => this.filterPosts(posts).map(post => <Post />) } </PostLoader> ) } } 

PostList ne change que si nous modifions le rendu de la liste des articles. Semble évident, non? C'est exactement ce dont nous avons besoin.

PostLoader ne change que si nous changeons la façon dont les messages sont chargés. Et enfin, Post ne change que si nous changeons la façon dont le post est rendu.

Conclusion


Tous ces composants sont minuscules et remplissent une petite fonction. Les raisons de ces modifications sont faciles à identifier et les composants eux-mêmes sont testés et corrigés.

Maintenant, notre application est beaucoup plus facile à modifier - réorganiser les composants, ajouter de nouvelles fonctionnalités et étendre les fonctionnalités existantes. Vous avez juste besoin de regarder un fichier de composant pour déterminer à quoi il sert.

Nous savons que nos composants changeront et grandiront avec le temps, mais l'application de cette règle universelle vous aidera à éviter les dettes techniques et à augmenter la vitesse de l'équipe. La répartition des composants dépend de vous, mais n'oubliez pas - il ne doit y avoir qu'une seule raison pour changer le composant .

Merci de votre attention et attendons vos commentaires avec impatience!

Source: https://habr.com/ru/post/fr437504/


All Articles