Le designer a-t-il une nouvelle idée? Quoi de plus simple

Bonjour, habrovchanin! Les concepteurs sont des gens idéologiques et des clients, en particulier avec leurs besoins commerciaux.

Imaginez que vous avez empilé votre meilleur UIkit au monde au plus cool% insérez votre framework% JS. Il semblerait qu'il y ait tout ce dont le projet a besoin. Vous pouvez maintenant boire du café et fermer toutes les nouvelles tâches en lançant des composants sur la page. Encore mieux, si vous avez trouvé un tel UIkit dans une décharge sur les espaces ouverts NPM et qu'il correspond parfaitement à l'UX / UI actuel et à ses besoins. Fiction!

Et vraiment ... à qui je plaisante? Votre bonheur est susceptible d'être de courte durée. Après tout, lorsque le concepteur lance le Talmud de nouvelles solutions d'interface utilisateur pour la page suivante ou «projet spécial», quelque chose va mal de toute façon.

À ce stade, le développeur est confronté à la question "DRY or not DRY" ? Dois-je en quelque sorte personnaliser les composants existants? Oui, afin de ne pas retarder la régression sur les cas existants. Ou agissez selon le principe «fonctionne - ne touchez pas» et écrivez de nouveaux composants à partir de zéro. Dans le même temps, gonfler UIkit et compliquer le support.

Si vous, comme beaucoup, avez été dans une telle situation, regardez sous la coupe!



Malgré la longue introduction, l'idée d'écrire cet article m'est venue après avoir lu l' un des fils de commentaires sur Habré. Là, les gars ont été sérieusement pulvérisés sur la façon de personnaliser le composant bouton sur React. Eh bien, après avoir regardé deux de ces holivars dans Telegram, la nécessité d'écrire à ce sujet a finalement été renforcée.

Pour commencer, essayons d'imaginer quelles «personnalisations» nous pourrions avoir besoin d'appliquer au composant.

Les styles



Tout d'abord, il s'agit de la personnalisation des styles de composants. Un exemple courant est un bouton gris, mais un bleu est nécessaire. Ou un bouton sans coins arrondis et soudain, ils sont nécessaires. Sur la base des holivars que j'ai lus, j'ai conclu qu'il existe environ 3 approches pour cela:

1. Styles globaux


Utilisez toute la puissance des styles CSS globaux, plutôt redirigés par ! Important , afin que l'extérieur, globalement, essaie de chevaucher les styles du composant d'origine. La décision, pour le moins, est controversée et trop simple. De plus, une telle option n'est tout simplement pas toujours possible et, en même temps, viole désespérément toute encapsulation de styles. Sauf s'il est bien sûr utilisé dans vos composants.

2. Passer des classes (styles) à partir du contexte parent


Aussi une décision assez controversée. Il s'avère que nous créons un accessoire spécial, pour ainsi dire, par exemple, nous l'appellerons classes et juste au-dessus, nous immergons les classes nécessaires dans le composant.

<Button classes="btn-red btn-rounded" /> 

Naturellement, une telle approche ne fonctionnera que si le composant prend en charge l'application de styles à son contenu de cette manière. De plus, si le composant est un peu plus complexe et consiste en une structure imbriquée d'éléments HTML, alors évidemment appliquer des styles à tous sera problématique. Ainsi, ils seront appliqués à l'élément racine du composant, puis, en utilisant les règles CSS, ils se répandront d'une manière ou d'une autre. Malheureusement.

3. Définition du composant à l'aide d'accessoires


Cela semble être la solution la plus sensée, mais en même temps la moins flexible. Autrement dit, nous nous attendons à ce que l'auteur du composant soit une sorte de génie et réfléchisse à l'avance à toutes les options. Autrement dit, tout ce dont nous pouvons avoir besoin et déterminé tous les accessoires nécessaires pour tous les résultats souhaités:

 <Button bgColor="red" rounded={true} /> 

Cela ne semble pas très probable, hein? Peut-être.

Comportement




Ici, c'est encore plus ambigu. Tout d'abord, car les difficultés de personnalisation du comportement d'un composant proviennent de la tâche. Plus le composant et sa logique sont complexes, et plus le changement que nous voulons apporter est complexe, plus il est difficile de faire ce changement. Une sorte de tautologie s'est avérée ... Bref, vous comprenez! ;-)

Cependant, même ici, il existe un ensemble d'outils qui nous aident à personnaliser le composant ou non. Comme nous parlons spécifiquement de l'approche par composants, je voudrais souligner les outils utiles suivants:

1. Travail pratique avec des accessoires


Tout d'abord, vous devez être capable d'imiter un ensemble d'accessoires de composants sans avoir à redécrire cet ensemble et à les proxy plus loin.

De plus, si nous essayons d'ajouter un certain comportement au composant, alors, très probablement, nous devrons utiliser un ensemble supplémentaire d'accessoires qui ne sont pas nécessaires au composant d'origine. Par conséquent, il est bon de pouvoir couper une partie des accessoires et de ne transférer que le nécessaire au composant d'origine. En même temps, garder toutes les propriétés synchronisées.

Le revers est quand nous voulons implémenter un cas spécial de comportement de composant. En quelque sorte pour fixer une partie de son état sur une tâche spécifique.

2. Suivi du cycle de vie des composants et des événements


En d'autres termes, tout ce qui se passe à l'intérieur d'un composant ne doit pas être un livre complètement fermé. Sinon, cela complique vraiment la personnalisation de son comportement.

Je ne parle pas de violation d'encapsulation et d'interférence incontrôlée à l'intérieur. Le composant doit être géré via son API publique (il s'agit généralement d'accessoires et / ou de méthodes). Mais pour pouvoir en quelque sorte «découvrir» ce qui se passe à l'intérieur et suivre le changement de son état est toujours nécessaire.

3. Gestion impérative


Nous supposerons que je ne vous ai pas dit cela. Et pourtant, parfois, c'est bien de pouvoir obtenir une instance d'un composant et impérativement "tirer les cordes". Il vaut mieux éviter cela, mais dans des cas particulièrement complexes, vous ne pouvez pas vous en passer.

Ok, en quelque sorte réglé la théorie. Dans l'ensemble, tout est évident, mais tout n'est pas clair. Par conséquent, il vaut la peine d'envisager au moins un cas réel.

Cas




J'ai mentionné ci-dessus que l'idée d'écrire un article est née de l'holivar sur la personnalisation d'un bouton. Par conséquent, j'ai pensé qu'il serait symbolique de résoudre un tel cas. Changer la couleur stupidement ou arrondir les coins serait trop facile, j'ai donc essayé de proposer un boîtier un peu plus complexe.

Imaginez que nous ayons un certain composant du bouton de base, qui est utilisé dans la prison des emplacements d'application. En outre, il implémente un comportement de base pour tous les boutons d'application, ainsi qu'un ensemble de styles encapsulés de base, qui, de temps en temps, sont synchronisés avec les guides d'interface utilisateur et tout cela.

De plus, il devient nécessaire d'avoir un composant supplémentaire pour le bouton d'envoi au serveur (bouton d'envoi), qui, en plus des changements de style, nécessite un comportement supplémentaire. Par exemple, il peut s'agir d'un dessin de la progression de l'envoi, ainsi que d'une représentation visuelle du résultat de cette action - avec ou sans succès.

Cela pourrait ressembler à ceci:



Il n'est pas difficile de deviner que le bouton de base se trouve à gauche et que le bouton d'envoi à droite est en état de réussite de la demande. Eh bien, si l'affaire est claire - commençons!

Solution


Je ne pouvais toujours pas comprendre ce qui avait exactement causé l'holivar dans la décision sur React. Apparemment, ce n'est pas si simple. Par conséquent, je ne vais pas tenter ma chance et utiliser l'outil plus familier - SvelteJS - un cadre disparaissant de nouvelle génération qui est presque parfait pour résoudre de tels problèmes .

Nous sommes immédiatement d'accord, nous n'interférerons en aucune façon avec le code du bouton de base. Nous supposons qu'il n'a pas été écrit par nous du tout, et son code est fermé pour corrections. Dans ce cas, le composant du bouton de base ressemblera à ceci:

Button.html

 <button {type} {name} {value} {disabled} {autofocus} on:click > <slot></slot> </button> <script> export default { data() { return { type: 'button', disabled: false, autofocus: false, value: '', name: '' }; } }; </script> <style> /* scoped styles */ </style> 

Et utilisé de cette façon:

 <Button on:click="cancel()">Cancel</Button> 

Veuillez noter que le composant bouton est vraiment très basique. Il ne contient absolument aucun élément auxiliaire ou accessoire pouvant aider à la mise en œuvre de la version étendue du composant. Ce composant ne prend même pas en charge le transfert de styles par des accessoires ou au moins une sorte de personnalisation intégrée, et tous les styles sont strictement isolés et ne fuient pas.

Créer à partir de ce composant un autre, avec des fonctionnalités améliorées et même sans apporter de modifications, peut sembler une tâche facile. Mais pas lorsque vous utilisez Svelte .

Déterminons maintenant ce que le bouton Soumettre devrait être capable de faire:

  1. Tout d'abord, le texte du cadre et du bouton doit être vert. En vol stationnaire, l'arrière-plan doit également être vert au lieu de gris foncé.
  2. De plus, lorsqu'un bouton est enfoncé, il devrait «claquer» en un indicateur de progression rond.
  3. À la fin du processus (qui est contrôlé en externe), il est nécessaire que l'état du bouton puisse être changé en réussi (succès) ou non réussi (erreur). Dans le même temps, le bouton de l'indicateur devrait se transformer soit en un badge vert avec une patte, soit un badge rouge avec une croix.
  4. Il est également nécessaire de pouvoir régler le temps après lequel le badge correspondant se transformera à nouveau en bouton dans son état d'origine (inactif).
  5. Et bien sûr, vous devez faire tout cela au-dessus du bouton de base en enregistrant et en appliquant tous les styles et accessoires à partir de là.

Fuh, pas une tâche facile. Créons d'abord un nouveau composant et enveloppons-le avec le bouton de base:

SubmitButton.html

 <Button> <slot></slot> </Button> <script> import Button from './Button.html'; export default { components: { Button } }; </script> 

Bien que ce soit exactement le même bouton, pire encore - il ne sait même pas comment proxy les accessoires. Cela n'a pas d'importance, nous y reviendrons plus tard.

Styliser


En attendant, réfléchissons à la façon dont nous pouvons styliser un nouveau bouton, à savoir changer les couleurs, selon la tâche. Malheureusement, il semble que nous ne pouvons utiliser aucune des approches décrites ci-dessus.

Les styles étant isolés à l'intérieur d'un bouton, des problèmes avec les styles globaux peuvent survenir. Il est également impossible d'obtenir des styles à l'intérieur - le bouton de base ne prend tout simplement pas en charge cette fonctionnalité. En plus de personnaliser l'apparence à l'aide d'accessoires. De plus, nous aimerions que tous les styles écrits pour le nouveau bouton soient également encapsulés à l'intérieur de ce bouton et ne fuient pas.

La solution est incroyablement simple, mais uniquement si vous utilisez déjà Svelte . Donc, écrivez simplement les styles du nouveau bouton:

 <div class="submit"> <Button> <slot></slot> </Button> </div> ... <style> .submit :global(button) { border: 2px solid #1ECD97; color: #1ECD97; } .submit :global(button:hover) { background-color: #1ECD97; color: #fff; } </style> 

L'une des notes clés de Svelte - les choses simples doivent être résolues simplement. Le modificateur spécial : global dans cette version va générer du CSS de telle sorte que seuls les boutons à l'intérieur du bloc avec la classe d' envoi qui sont dans ce composant recevront les styles spécifiés.

Même si le balisage du même type apparaît soudainement à tout autre endroit de l'application:

 <div class="submit"> <button>Button</button> </div> 

Les styles du composant SubmitButton ne «fuient» en aucune façon.

En utilisant cette méthode, Svelte facilite la personnalisation facile des styles des composants imbriqués, tout en préservant l'encapsulation des styles des deux composants.

Nous jetons des accessoires et fixons le comportement


Eh bien, nous avons traité le style presque instantanément et sans accessoires supplémentaires et en passant directement les classes CSS. Maintenant, vous devez proxy tous les accessoires du composant Button via le nouveau composant. Cependant, je ne voudrais pas les décrire à nouveau. Cependant, pour commencer, décidons des propriétés du nouveau composant.

À en juger par la tâche, SubmitButton doit surveiller l'état, ainsi que fournir une opportunité de spécifier le délai entre le changement automatique d'un état réussi / d'erreur à l'état initial:

 <script> ... export default { ... data() { return { delay: 1500, status: 'idle' // loading, success, error }; } }; </script> 

Ainsi, notre nouveau bouton aura 4 états: repos, téléchargement, succès ou erreur. De plus, par défaut, les 2 derniers états passeront automatiquement à l'état inactif après 1,5 seconde.

Afin de jeter tous les accessoires transmis dans le composant Button , mais en même temps couper le statut et le retard qui sont évidemment invalides pour lui, nous allons écrire une propriété calculée spéciale. Et après cela, nous utilisons simplement l'opérateur de propagation pour « étaler » les accessoires restants sur le composant intégré. De plus, comme nous faisons exactement le bouton d'envoi, nous devons corriger le type de bouton afin qu'il ne puisse pas être modifié de l'extérieur:

 <div class="submit"> <Button {...attrs} type="submit"> <slot></slot> </Button> </div> <script> ... export default { ... computed: { attrs: data => { const { delay, status, ...attrs } = data; return attrs; } }, }; </script> 

Assez simple et élégant.

En conséquence, nous avons obtenu une version entièrement fonctionnelle du bouton de base avec des styles modifiés. Il est temps d'implémenter le nouveau comportement des boutons.

Nous changeons et suivons le statut


Ainsi, lorsque vous cliquez sur le bouton SubmitButton, nous devons non seulement supprimer l'événement afin que le code utilisateur puisse le traiter (comme cela se fait dans Button ), mais également implémenter une logique métier supplémentaire - définir l'état de téléchargement. Pour ce faire, saisissez simplement l'événement du bouton de base dans votre propre gestionnaire, faites ce dont vous avez besoin et envoyez-le plus loin:

 <div class="submit"> <Button {...attrs} type="submit" on:click="click(event)"> <slot></slot> </Button> </div> <script> ... export default { ... methods: { click(e) { this.set({ status: 'loading' }); this.fire('click', e); } }, }; </script> 

En outre, le composant parent de ce bouton, qui contrôle le processus d'envoi des données lui-même, peut définir l'état d'envoi correspondant ( succès / erreur ) via des accessoires. Dans le même temps, le bouton doit suivre un tel changement d'état et, après un temps spécifié, changer automatiquement l'état en veille . Pour ce faire, utilisez le hook de mise à jour du cycle de vie :

 <script> ... export default { ... onupdate({ current: { status, delay }, changed }) { if (changed.status && ['success', 'error'].includes(status)) { setTimeout(() => this.set({ status: 'idle' }), delay); } }, }; </script> 

Touche finale


Il y a 2 autres points qui ne sont pas évidents à partir de la tâche et qui surviennent lors de la mise en œuvre. Tout d'abord, pour que l'animation des métamorphoses des boutons soit fluide, vous devrez modifier le bouton lui-même avec les styles, et non avec un autre élément. Pour ce faire, nous pouvons utiliser le même : global , donc il n'y a pas de problème. Mais en plus, il est nécessaire que le balisage à l'intérieur du bouton soit caché dans tous les états, sauf inactif .

Il convient de mentionner séparément que le balisage à l'intérieur du bouton peut être quelconque et qu'il est jeté dans le composant d'origine du bouton de base via des emplacements imbriqués. Cependant, bien que cela semble menaçant, la solution est plus que primitive - il vous suffit d'envelopper le slot à l'intérieur du nouveau composant dans un élément supplémentaire et de lui appliquer les styles nécessaires:

 <div class="submit"> <Button {...attrs} type="submit" on:click="click(event)"> <span><slot></slot></span> </Button> </div> ... <style> ... .submit span { transition: opacity 0.3s 0.1s; } .submit.loading span, .submit.success span, .submit.error span { opacity: 0; } ... </style> 

De plus, puisque le bouton ne se cache pas de la page, mais se transforme avec les statuts, il serait bien de le désactiver au moment de l'envoi. En d'autres termes, si le bouton d'envoi a été défini sur désactivé à l' aide d'accessoires ou si l'état n'est pas inactif , vous devez désactiver le bouton. Pour résoudre ce problème, nous écrivons une autre petite propriété calculée isDisabled et l'appliquons au composant imbriqué:

 <div class="submit"> <Button {...attrs} type="submit" disabled={isDisabled}> <span><slot></slot></span> </Button> </div> <script> ... export default { ... computed: { ... isDisabled: ({ status, disabled }) => disabled || status !== 'idle' }, }; </script> 

Tout irait bien, mais le bouton de base a un style qui le rend translucide à l'état désactivé, mais nous n'en avons pas besoin si le bouton n'est désactivé que temporairement en raison d'un changement de statut. Tout de même vient à la rescousse : global :

  .submit.loading :global(button[disabled]), .submit.success :global(button[disabled]), .submit.error :global(button[disabled]) { opacity: 1; } 

C'est tout! Le nouveau bouton est magnifique et prêt à l'emploi!



Je vais intentionnellement omettre les détails de la mise en œuvre des animations et tout cela. Non seulement parce qu'elle n'est pas directement liée au sujet de l'article, mais aussi parce que dans cette partie la démo ne s'est pas déroulée comme nous le souhaiterions. Je n'ai pas compliqué ma tâche et mis en œuvre une solution complètement clé en main pour un tel bouton et porté assez bêtement un exemple trouvé sur Internet.

Par conséquent, je ne conseille pas d'utiliser cette implémentation dans le travail. N'oubliez pas, ce n'est qu'une démonstration de cet article.

Démo interactive et exemple de code complet

Si vous avez aimé l'article et que vous vouliez en savoir plus sur Svelte , lisez d' autres articles . Par exemple, «Comment faire une recherche d'utilisateur sur GitHub sans React + RxJS 6 + Recomposer» . Écoutez le podcast du Nouvel An RadioJS # 54 , où j'ai parlé en détail de ce qu'est Svelte , comment il «disparaît» et pourquoi ce n'est pas «encore un autre framework js».

Découvrez la chaîne de télégramme en langue russe SvelteJS . Nous sommes déjà plus de deux cents et nous serons ravis de vous voir!

P / s

Du coup, les directives de l'interface utilisateur ont changé. Maintenant, les étiquettes de tous les boutons de l'application doivent être en majuscules. Cependant, nous n'avons pas peur d'une telle tournure des événements. Ajoutez text-transform: majuscule; dans les styles du bouton de base et continuer à boire du café.

Bonne journée de travail!

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


All Articles