Dans le document dont nous publions la traduction aujourd'hui, nous parlerons de la façon de créer une grille typographique pour le système de conception en utilisant les
fonctions de rendu Vue .
Voici une démo du projet que nous allons revoir ici.
Vous pouvez trouver son code ici. L'auteur de ce document dit qu'il a utilisé des fonctions de rendu car elles permettent un contrôle beaucoup plus précis du processus de création de code HTML que les modèles Vue classiques. Cependant, à sa grande surprise, il n'a pu trouver d'exemples pratiques de leur application. Il n'a rencontré que des manuels d'instructions. Il espère que ce matériau fera une différence pour le mieux grâce à l'exemple pratique d'utilisation des fonctions de rendu de Vue.
Fonctions de rendu Vue
Les fonctions de rendu m'ont toujours semblé quelque chose d'un peu inhabituel pour Vue. Tout dans ce cadre souligne le désir de simplicité et la séparation des fonctions des différentes entités. Mais les fonctions de rendu sont un étrange mélange de HTML et de JavaScript, qui est souvent difficile à lire.
Par exemple, voici le balisage HTML:
<div class="container"> <p class="my-awesome-class">Some cool text</p> </div>
Pour le former, vous avez besoin de la fonction suivante:
render(createElement) { return createElement("div", { class: "container" }, [ createElement("p", { class: "my-awesome-class" }, "Some cool text") ]) }
Je soupçonne que de telles constructions feront que beaucoup se détourneront immédiatement des fonctions de rendu. Après tout, la facilité d'utilisation est exactement ce qui attire les développeurs vers Vue. Il est dommage que beaucoup de gens ne voient pas leurs vrais mérites derrière l'apparence disgracieuse des fonctions de rendu. Le fait est que les fonctions de rendu et les composants fonctionnels sont des outils intéressants et puissants. Afin de démontrer leurs capacités et leur véritable valeur, je parlerai de la façon dont ils m'ont aidé à résoudre le vrai problème.
Veuillez noter qu'il sera très utile d'ouvrir une
version de démonstration du projet considéré dans un onglet de navigateur adjacent et d'y accéder pendant la lecture d'un article.
Définition de critères pour un système de conception
Nous avons un système de conception basé sur
VuePress . Nous devions y inclure une nouvelle page, démontrant diverses possibilités typographiques de conception de texte. Voilà à quoi ressemblait la mise en page que le designer m'a donnée.
Mise en pageEt voici un exemple du code CSS correspondant à cette page:
h1, h2, h3, h4, h5, h6 { font-family: "balboa", sans-serif; font-weight: 300; margin: 0; } h4 { font-size: calc(1rem - 2px); } .body-text { font-family: "proxima-nova", sans-serif; } .body-text--lg { font-size: calc(1rem + 4px); } .body-text--md { font-size: 1rem; } .body-text--bold { font-weight: 700; } .body-text--semibold { font-weight: 600; }
Les en-têtes sont formatés en fonction des noms de balises. Pour formater d'autres éléments, des noms de classe sont utilisés. De plus, il existe des classes distinctes pour la richesse et les tailles de police.
Avant de commencer à écrire du code, j'ai formulé quelques règles:
Options pour résoudre le problème
Avant de commencer le travail, j'ai envisagé plusieurs options pour résoudre l'ensemble de tâches qui m'était proposé. Voici leur aperçu.
WritingÉcrire manuellement du code HTML
J'aime écrire du code HTML manuellement, mais seulement quand cela nous permet de résoudre adéquatement le problème existant. Cependant, dans mon cas, l'écriture manuelle de code impliquerait la saisie de divers fragments de code répétitifs dans lesquels certaines variations sont présentes. Je n'aimais pas ça. De plus, cela signifierait que les données ne pourraient pas être stockées dans un fichier séparé. En conséquence, j'ai refusé cette approche.
Si j'avais créé la page en question comme ça, j'aurais eu quelque chose comme ceci:
<div class="row"> <h1>Heading 1</h1> <p class="body-text body-text--md body-text--semibold">h1</p> <p class="body-text body-text--md body-text--semibold">Balboa Light, 30px</p> <p class="group body-text body-text--md body-text--semibold"> <span>Product title (once on a page)</span> <span>Illustration headline</span> </p> </div>
▍Utilisation des modèles Vue traditionnels
Dans des conditions normales, cette approche est utilisée le plus souvent. Cependant, jetez un œil à
cet exemple.
Un exemple d'utilisation de modèles VueDans la première colonne, il y a ce qui suit:
- La
<h1>
, présentée sous la forme dans laquelle le navigateur l'affiche. - La
<p>
regroupant plusieurs enfants <span>
avec du texte. Une classe est affectée à chacun de ces éléments (mais aucune classe spéciale n'est affectée à la <p>
elle-même). - La
<p>
qui n'a pas d'éléments <span>
imbriqués auxquels la classe est affectée.
Pour implémenter tout cela, de nombreuses instances de directives
v-if
et
v-if-else
seraient nécessaires. Et cela, je le sais, conduirait au fait que le code deviendrait très déroutant très bientôt. De plus, je n'aime pas l'utilisation de toute cette logique conditionnelle dans le balisage.
▍Fonctions de rendu
En conséquence, après avoir analysé les alternatives possibles, j'ai sélectionné les fonctions de rendu. En eux, en utilisant JavaScript, en utilisant des constructions conditionnelles, des nœuds enfants d'autres nœuds sont créés. Lors de la création de ces nœuds enfants, tous les critères nécessaires sont pris en compte. Dans cette situation, une telle solution me semblait parfaite.
Modèle de données
Comme je l'ai dit, je voulais stocker les données typographiques dans un fichier JSON distinct. Cela permettrait, si nécessaire, d'y apporter des modifications sans toucher au balisage.
Ce sont les données.
Chaque objet JSON du fichier est une description d'une ligne distincte:
{ "text": "Heading 1", "element": "h1", // . "properties": "Balboa Light, 30px", // . "usage": ["Product title (once on a page)", "Illustration headline"] // . - . }
Voici le code HTML qui vient après le traitement de cet objet:
<div class="row"> <h1>Heading 1</h1> <p class="body-text body-text--md body-text--semibold">h1</p> <p class="body-text body-text--md body-text--semibold">Balboa Light, 30px</p> <p class="group body-text body-text--md body-text--semibold"> <span>Product title (once on a page)</span> <span>Illustration headline</span> </p> </div>
Considérons maintenant un exemple plus complexe. Les tableaux représentent des groupes d'enfants. Les propriétés des objets
classes
, qui sont eux-mêmes des objets, peuvent stocker des descriptions de classe. La propriété de
base
de l'objet
classes
contient une description des classes communes à tous les nœuds de la cellule. Chaque classe présente dans la propriété
variants
est appliquée à un seul élément du groupe.
{ "text": "Body Text - Large", "element": "p", "classes": { "base": "body-text body-text--lg",
Cet objet se transforme en HTML suivant:
<div class="row"> <p class="group"> <span class="body-text body-text--lg body-text--bold">Body Text - Large</span> <span class="body-text body-text--lg body-text--regular">Body Text - Large</span> </p> <p class="group body-text body-text--md body-text--semibold"> <span>body-text body-text--lg body-text--bold</span> <span>body-text body-text--lg body-text--regular</span> </p> <p class="body-text body-text--md body-text--semibold">Proxima Nova Bold and Regular, 20px</p> <p class="group body-text body-text--md body-text--semibold"> <span>Large button title</span> <span>Form label</span> <span>Large modal text</span> </p> </div>
La structure de base du projet
Nous avons un composant parent,
TypographyTable.vue
, qui contient un balisage pour former la table. Nous avons également un composant enfant,
TypographyRow.vue
, qui est responsable de la création de la ligne du tableau et contient notre fonction de rendu.
Lors de la formation de lignes de table, un tableau contenant des données est parcouru. Les objets qui décrivent les lignes de la table sont passés au composant
TypographyRow
tant que propriétés.
<template> <section> <div class="row"> <p class="body-text body-text--lg-bold heading">Hierarchy</p> <p class="body-text body-text--lg-bold heading">Element/Class</p> <p class="body-text body-text--lg-bold heading">Properties</p> <p class="body-text body-text--lg-bold heading">Usage</p> </div> <typography-row v-for="(rowData, index) in $options.typographyData" :key="index" :row-data="rowData" /> </section> </template> <script> import TypographyData from "@/data/typography.json"; import TypographyRow from "./TypographyRow"; export default {
Ici, je voudrais noter une petite chose sympa: les données typographiques dans une instance de Vue peuvent être représentées comme une propriété. Vous pouvez y accéder en utilisant la construction
$options.typographyData
, car ils ne changent pas et ne doivent pas être réactifs (merci à
Anton Kosykh ).
Création d'un composant fonctionnel
Le composant
TypographyRow
qui traite les données est un composant fonctionnel. Les composants fonctionnels sont des entités qui n'ont ni états ni instances. Cela signifie qu'ils ne l'ont pas et qu'ils n'ont pas accès aux méthodes de cycle de vie du composant Vue.
Voici le «squelette» d'un composant similaire avec lequel nous allons commencer à travailler sur notre composant:
La méthode du composant de
render
prend un argument de
context
qui a une propriété
props
. Cette propriété est sujette à la déstructuration et est utilisée comme deuxième argument.
Le premier argument est
createElement
. Il s'agit d'une fonction qui indique à Vue quel nœud créer. Par souci de brièveté et de normalisation du code, j'utilise l'abréviation
h
pour
createElement
. Vous pouvez lire pourquoi je l'ai fait
ici .
Donc,
h
prend trois arguments:
- Balise HTML (par exemple
div
). - Un objet de données contenant des attributs de modèle (par exemple,
{ class: 'something'}
). - Chaînes de texte (si nous ajoutons simplement du texte) ou nœuds enfants créés à l'aide de
h
.
Voici à quoi ça ressemble:
render(h, { props }) { return h("div", { class: "example-class" }, "Here's my example text") }
Résumons ce que nous avons déjà créé. À savoir, nous avons maintenant les éléments suivants:
- Un fichier avec des données qui est prévu pour être utilisé dans la formation de la page.
- Un composant Vue standard qui importe un fichier de données.
- Le cadre du composant fonctionnel qui est responsable de l'affichage des lignes de la table.
Pour créer des lignes de table, les données du format JSON doivent être passées en argument à
h
. Vous pouvez transférer toutes ces données en une seule fois, mais avec cette approche, vous aurez besoin d'une grande quantité de logique conditionnelle, ce qui dégradera la lisibilité du code. Au lieu de cela, j'ai décidé de faire ceci:
- Transformez les données en un format standardisé.
- Affichez les données transformées.
Transformation des données
Je souhaite que mes données soient présentées dans un format correspondant aux arguments acceptés par
h
. Mais avant de les convertir, j'ai prévu quelle structure ils devraient avoir dans le fichier JSON:
// { tag: "", // HTML- cellClass: "", // . - null text: "", // , children: [] // , . , . }
Chaque objet représente une cellule du tableau. Quatre cellules forment chaque ligne du tableau (elles sont rassemblées dans un tableau):
// [ { cell1 }, { cell2 }, { cell3 }, { cell4 } ]
Le point d'entrée peut être une fonction comme celle-ci:
function createRow(data) {
Regardons à nouveau la disposition.
Mise en pageVous pouvez voir que dans la première colonne, les éléments sont de style différent. Et dans les colonnes restantes, le même formatage est utilisé. Commençons donc par ça.
Permettez-moi de vous rappeler que je voudrais utiliser la structure JSON suivante comme modèle pour décrire chaque cellule:
{ tag: "", cellClass: "", text: "", children: [] }
Avec cette approche, une structure arborescente sera utilisée pour décrire chaque cellule. C'est précisément parce que certaines cellules contiennent des groupes d'enfants. Nous utilisons les deux fonctions suivantes pour créer des cellules:
- La fonction
createNode
prend chacune des propriétés qui nous intéressent comme argument. - La fonction
createCell
joue le rôle d'un wrapper autour de createNode
, avec son aide, nous vérifions si le text
l'argument text
tableau. Si c'est le cas, nous créons un tableau d'enfants.
Maintenant, nous pouvons faire quelque chose comme ça:
function createRow(data) { let { text, element, classes = null, properties, usage } = data; let row = []; row[0] = "" row[1] = createCellData("p", ?????)
Lors de la formation des troisième et quatrième colonnes, nous transmettons les
properties
et l'
usage
comme arguments de texte. Cependant, la deuxième colonne est différente des troisième et quatrième. Ici, nous affichons les noms des classes, qui, dans les données source, sont stockées sous cette forme:
"classes": { "base": "body-text body-text--lg", "variants": ["body-text--bold", "body-text--regular"] },
De plus, n'oublions pas que lorsque vous travaillez avec des en-têtes, les classes ne sont pas utilisées. Par conséquent, nous devons créer des noms de balises d'en-tête pour les lignes correspondantes (c'est-à-dire
h1
,
h2
, etc.).
Nous créons des fonctions auxiliaires qui nous permettent de convertir ces données dans un format qui facilite leur utilisation comme argument de
text
.
Maintenant, nous pouvons faire ce qui suit:
function createRow(data) { let { text, element, classes = null, properties, usage } = data; let row = []; row[0] = "" row[1] = createCellData("p", displayClasses(element, classes))
Transformation des données utilisées pour démontrer les styles
Nous devons décider quoi faire avec la première colonne du tableau, montrant des exemples d'application de styles. Cette colonne est différente des autres. Ici, nous appliquons de nouvelles balises et classes à chaque cellule au lieu d'utiliser la combinaison de classes utilisée par les colonnes restantes:
<p class="body-text body-text--md body-text--semibold">
Au lieu d'essayer d'implémenter cette fonctionnalité dans
createCellData
ou
createNodeData
, je propose de créer une nouvelle fonction qui tirera parti des capacités de ces fonctions de base qui effectuent la transformation des données. Il mettra en œuvre un nouveau mécanisme de traitement des données:
function createDemoCellData(data) { let children; const classes = getClasses(data.classes);
Maintenant, les données de chaîne sont réduites à un format normalisé et peuvent être passées à la fonction de rendu:
function createRow(data) { let { text, element, classes = null, properties, usage } = data let row = [] row[0] = createDemoCellData(data) row[1] = createCellData("p", displayClasses(element, classes)) row[2] = createCellData("p", properties) row[3] = createCellData("p", usage) return row }
Rendu des données
Voici comment rendre les données affichées sur la page:
Maintenant,
tout est prêt !
Ici encore, le code source.
Résumé
Il vaut la peine de dire que l'approche considérée ici est une manière expérimentale de résoudre un problème plutôt trivial. Je suis sûr que beaucoup diront que cette solution est excessivement compliquée et surchargée d'excès d'ingénierie. Je serai peut-être d'accord avec cela.
Malgré le fait que le développement de ce projet ait pris beaucoup de temps, les données sont désormais complètement séparées de la présentation. Maintenant, si nos concepteurs viennent ajouter des lignes au tableau ou en supprimer des lignes existantes, je n'ai pas besoin de ratisser le code HTML
déroutant . Pour ce faire, il me suffira de modifier plusieurs propriétés dans le fichier JSON.
Le résultat vaut-il l'effort? Je pense qu'il faut regarder les circonstances. Ceci, cependant, est très caractéristique de la programmation. Je veux dire que dans ma tête, dans le processus de travail sur ce projet, l'image suivante est constamment apparue.
C'est peut-être la réponse à ma question de savoir si ce projet vaut l'effort consacré à son développement.
Chers lecteurs! ? ?
