In dem Material, dessen Übersetzung wir heute veröffentlichen, werden wir darüber sprechen, wie mit den
Vue-Renderfunktionen ein Druckraster für das
Designsystem erstellt wird .
Hier ist eine Demo des Projekts, die wir hier überprüfen werden.
Den Code finden Sie hier. Der Autor dieses Materials gibt an, dass er Renderfunktionen verwendet hat, da diese eine viel genauere Kontrolle über den Prozess der Erstellung von HTML-Code ermöglichen als normale Vue-Vorlagen. Zu seiner Überraschung konnte er jedoch keine praktischen Beispiele für ihre Anwendung finden. Er stieß nur auf Bedienungsanleitungen. Er hofft, dass dieses Material dank des praktischen Beispiels für die Verwendung der Renderfunktionen von Vue hier einen Unterschied zum Besseren bewirken wird.
Vue-Renderfunktionen
Renderfunktionen schienen mir immer etwas, das für Vue etwas ungewöhnlich war. Alles in diesem Rahmen betont den Wunsch nach Einfachheit und Aufgabentrennung verschiedener Einheiten. Aber Renderfunktionen sind eine seltsame Mischung aus HTML und JavaScript, die oft schwer zu lesen ist.
Hier ist zum Beispiel das HTML-Markup:
<div class="container"> <p class="my-awesome-class">Some cool text</p> </div>
Um es zu bilden, benötigen Sie die folgende Funktion:
render(createElement) { return createElement("div", { class: "container" }, [ createElement("p", { class: "my-awesome-class" }, "Some cool text") ]) }
Ich vermute, dass solche Konstruktionen viele dazu bringen werden, sich sofort von Renderfunktionen abzuwenden. Schließlich ist die Benutzerfreundlichkeit genau das, was Entwickler zu Vue anzieht. Es ist schade, wenn viele Menschen ihre wahren Vorzüge nicht hinter dem unansehnlichen Auftreten von Renderfunktionen sehen. Die Sache ist, dass Renderfunktionen und Funktionskomponenten interessante und leistungsstarke Werkzeuge sind. Um ihre Fähigkeiten und ihren wahren Wert zu demonstrieren, werde ich darüber sprechen, wie sie mir geholfen haben, das eigentliche Problem zu lösen.
Bitte beachten Sie, dass es sehr nützlich ist, eine
Demoversion des betreffenden Projekts auf der Registerkarte des benachbarten Browsers zu öffnen und beim Lesen des Artikels darauf zuzugreifen.
Kriterien für ein Entwurfssystem definieren
Wir haben ein Design-System, das auf
VuePress basiert. Wir mussten eine neue Seite hinzufügen, die verschiedene typografische Möglichkeiten der Textgestaltung demonstrierte. So sah das Layout aus, das mir der Designer gegeben hatte.
SeitenlayoutUnd hier ist ein Beispiel für den CSS-Code, der dieser Seite entspricht:
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; }
Header werden basierend auf Tag-Namen formatiert. Zum Formatieren anderer Elemente werden Klassennamen verwendet. Darüber hinaus gibt es separate Klassen für Reichhaltigkeit und Schriftgröße.
Bevor ich anfing, Code zu schreiben, formulierte ich einige Regeln:
Optionen zur Lösung des Problems
Bevor ich mit der Arbeit anfing, dachte ich über verschiedene Möglichkeiten nach, um die vor mir liegende Aufgabe zu lösen. Hier ist ihre Übersicht.
▍ Manuelles Schreiben von HTML-Code
Ich schreibe HTML-Code gerne manuell, aber nur, wenn wir das bestehende Problem angemessen lösen können. In meinem Fall würde manuelles Schreiben von Code jedoch die Eingabe verschiedener sich wiederholender Codefragmente bedeuten, in denen einige Variationen vorhanden sind. Es hat mir nicht gefallen. Darüber hinaus würde dies bedeuten, dass die Daten nicht in einer separaten Datei gespeichert werden könnten. Infolgedessen lehnte ich diesen Ansatz ab.
Wenn ich die betreffende Seite einfach so erstellt hätte, hätte ich ungefähr Folgendes:
<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>
▍Verwenden traditioneller Vue-Muster
Unter normalen Bedingungen wird dieser Ansatz am häufigsten verwendet. Schauen Sie sich jedoch
dieses Beispiel an.
Ein Beispiel für die Verwendung von Vue-VorlagenIn der ersten Spalte steht Folgendes:
- Das
<h1>
, das in der Form angezeigt wird, in der der Browser es anzeigt. - Das
<p>
, das mehrere <span>
mit Text gruppiert. Jedem dieser Elemente wird eine Klasse zugewiesen (dem <p>
-Tag selbst wird jedoch keine spezielle Klasse zugewiesen). - Das
<p>
, das keine verschachtelten <span>
-Elemente enthält, denen die Klasse zugewiesen ist.
Um all dies zu implementieren,
v-if
viele Instanzen von
v-if
und
v-if-else
Anweisungen erforderlich. Und das würde, wie ich weiß, dazu führen, dass der Code sehr bald sehr verwirrend wird. Außerdem gefällt mir die Verwendung all dieser bedingten Logik im Markup nicht.
▍Renderfunktionen
Als Ergebnis habe ich nach Analyse der möglichen Alternativen Renderfunktionen ausgewählt. In ihnen werden unter Verwendung von JavaScript und unter Verwendung von bedingten Konstrukten untergeordnete Knoten anderer Knoten erstellt. Bei der Erstellung dieser untergeordneten Knoten werden alle erforderlichen Kriterien berücksichtigt. In dieser Situation schien mir eine solche Lösung perfekt zu sein.
Datenmodell
Wie gesagt, ich wollte typografische Daten in einer separaten JSON-Datei speichern. Dies würde es bei Bedarf ermöglichen, Änderungen daran vorzunehmen, ohne das Markup zu berühren.
Dies sind die Daten.
Jedes JSON-Objekt in der Datei ist eine Beschreibung einer separaten Zeile:
{ "text": "Heading 1", "element": "h1", // . "properties": "Balboa Light, 30px", // . "usage": ["Product title (once on a page)", "Illustration headline"] // . - . }
Hier ist der HTML-Code, der nach der Verarbeitung dieses Objekts kommt:
<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>
Betrachten Sie nun ein komplexeres Beispiel. Arrays repräsentieren Gruppen von Kindern. Die Eigenschaften von Klassenobjekten, die selbst Objekte sind, können Klassenbeschreibungen speichern. Die
base
des Klassenobjekts enthält eine Beschreibung der Klassen, die allen Knoten in der Zelle gemeinsam sind. Jede in der Eigenschaft varianten vorhandene Klasse wird auf ein einzelnes Element in der Gruppe angewendet.
{ "text": "Body Text - Large", "element": "p", "classes": { "base": "body-text body-text--lg",
Dieses Objekt wird in den folgenden HTML-Code umgewandelt:
<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>
Die Grundstruktur des Projekts
Wir haben eine übergeordnete Komponente,
TypographyTable.vue
, die Markups zur Bildung der Tabelle enthält. Wir haben auch eine untergeordnete Komponente,
TypographyRow.vue
, die für die Erstellung der Tabellenzeile verantwortlich ist und unsere Renderfunktion enthält.
Beim Bilden von Tabellenzeilen wird ein Array mit Daten durchlaufen. Objekte, die die Zeilen der Tabelle beschreiben, werden als Eigenschaften an die
TypographyRow
Komponente übergeben.
<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 {
An dieser Stelle möchte ich eine angenehme Kleinigkeit erwähnen: Die typografischen Daten in einer Vue-Instanz können als Eigenschaft dargestellt werden. Sie können mit dem Konstrukt
$options.typographyData
auf sie
$options.typographyData
, da sie sich nicht ändern und nicht reaktiv sein sollten (dank
Anton Kosykh ).
Funktionskomponente erstellen
Die
TypographyRow
Komponente, die Daten verarbeitet, ist eine Funktionskomponente. Funktionskomponenten sind Entitäten ohne Zustände und Instanzen. Dies bedeutet, dass sie dies nicht haben und keinen Zugriff auf die Lebenszyklusmethoden der Vue-Komponenten haben.
Hier ist das „Skelett“ einer ähnlichen Komponente, mit der wir mit der Arbeit an unserer Komponente beginnen werden:
Die
render
Komponentenmethode verwendet ein
context
mit einer
props
. Diese Eigenschaft unterliegt einer Destrukturierung und wird als zweites Argument verwendet.
Das erste Argument ist
createElement
. Dies ist eine Funktion, die Vue mitteilt, welcher Knoten erstellt werden soll. Der Kürze halber und zur Standardisierung des Codes verwende ich die Abkürzung
h
für
createElement
. Lesen Sie hier, warum ich das getan
habe .
Also braucht
h
drei Argumente:
- HTML-Tag (z. B.
div
). - Ein Datenobjekt, das Vorlagenattribute enthält (z. B.
{ class: 'something'}
). - Textzeichenfolgen (wenn wir nur Text hinzufügen) oder untergeordnete Knoten, die mit
h
.
So sieht es aus:
render(h, { props }) { return h("div", { class: "example-class" }, "Here's my example text") }
Fassen wir zusammen, was wir bereits erstellt haben. Wir haben jetzt nämlich folgendes:
- Eine Datei mit Daten, die für die Erstellung der Seite verwendet werden sollen.
- Eine reguläre Vue-Komponente, die eine Datendatei importiert.
- Das Framework der Funktionskomponente, das für die Anzeige der Tabellenzeilen verantwortlich ist.
Um Tabellenzeilen zu erstellen, müssen Daten aus dem JSON-Format als Argument an
h
. Sie können alle diese Daten auf einmal übertragen, aber bei diesem Ansatz benötigen Sie eine große Menge an bedingter Logik, die die Lesbarkeit des Codes beeinträchtigt. Stattdessen habe ich mich dazu entschlossen:
- Transformieren Sie Daten in ein standardisiertes Format.
- Transformierte Daten anzeigen.
Datentransformation
Ich möchte, dass meine Daten in einem Format dargestellt werden, das den von
h
akzeptierten Argumenten entspricht. Aber bevor ich sie konvertierte, plante ich, welche Struktur sie in der JSON-Datei haben sollten:
// { tag: "", // HTML- cellClass: "", // . - null text: "", // , children: [] // , . , . }
Jedes Objekt repräsentiert eine Zelle der Tabelle. Vier Zellen bilden jede Zeile der Tabelle (sie werden in einem Array gesammelt):
// [ { cell1 }, { cell2 }, { cell3 }, { cell4 } ]
Der Eingabepunkt kann eine Funktion wie die folgende sein:
function createRow(data) {
Schauen wir uns das Layout noch einmal an.
SeitenlayoutSie können sehen, dass in der ersten Spalte die Elemente unterschiedlich gestaltet sind. In den übrigen Spalten wird dieselbe Formatierung verwendet. Beginnen wir also damit.
Ich möchte Sie daran erinnern, dass ich die folgende JSON-Struktur als Modell für die Beschreibung jeder Zelle verwenden möchte:
{ tag: "", cellClass: "", text: "", children: [] }
Bei diesem Ansatz wird eine baumartige Struktur verwendet, um jede Zelle zu beschreiben. Dies liegt genau daran, dass einige Zellen Gruppen von Kindern enthalten. Wir verwenden die folgenden zwei Funktionen, um Zellen zu erstellen:
- Die Funktion
createNode
verwendet jede der Eigenschaften, an denen wir interessiert sind, als Argument. - Die Funktion
createCell
spielt die Rolle eines Wrappers um createNode
. Mit ihrer Hilfe überprüfen wir, ob der Argumenttext text
Array ist. In diesem Fall erstellen wir eine Reihe von untergeordneten Elementen.
Jetzt können wir so etwas machen:
function createRow(data) { let { text, element, classes = null, properties, usage } = data; let row = []; row[0] = "" row[1] = createCellData("p", ?????)
Beim Bilden der dritten und vierten Spalte übergeben wir
properties
und
usage
als Textargumente. Die zweite Spalte unterscheidet sich jedoch von der dritten und vierten. Hier zeigen wir die Namen der Klassen an, die in den Quelldaten in folgender Form gespeichert sind:
"classes": { "base": "body-text body-text--lg", "variants": ["body-text--bold", "body-text--regular"] },
Vergessen wir außerdem nicht, dass beim Arbeiten mit Headern keine Klassen verwendet werden. Daher müssen wir Header-Tag-Namen für die entsprechenden Zeilen erstellen (d.
h1
,
h2
usw.).
Wir erstellen Hilfsfunktionen, mit denen wir diese Daten in ein Format konvertieren können, das ihre Verwendung als Textargument erleichtert.
Jetzt können wir Folgendes tun:
function createRow(data) { let { text, element, classes = null, properties, usage } = data; let row = []; row[0] = "" row[1] = createCellData("p", displayClasses(element, classes))
Transformation von Daten zur Demonstration von Stilen
Wir müssen entscheiden, was mit der ersten Spalte der Tabelle geschehen soll, die Beispiele für die Anwendung von Stilen zeigt. Diese Spalte unterscheidet sich von den anderen. Hier wenden wir neue Tags und Klassen auf jede Zelle an, anstatt die von den verbleibenden Spalten verwendete Klassenkombination zu verwenden:
<p class="body-text body-text--md body-text--semibold">
Anstatt zu versuchen, diese Funktionalität in
createCellData
oder
createNodeData
zu implementieren, schlage ich vor, eine neue Funktion zu erstellen, die die Funktionen dieser Grundfunktionen zur Durchführung der Datentransformation nutzt. Es wird einen neuen Datenverarbeitungsmechanismus implementieren:
function createDemoCellData(data) { let children; const classes = getClasses(data.classes);
Jetzt werden die Zeichenfolgendaten auf ein normalisiertes Format reduziert und können an die Renderfunktion übergeben werden:
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 }
Datenwiedergabe
So rendern Sie die auf der Seite angezeigten Daten:
Jetzt ist
alles fertig !
Auch hier der Quellcode.
Zusammenfassung
Es ist erwähnenswert, dass der hier betrachtete Ansatz eine experimentelle Methode zur Lösung eines eher trivialen Problems ist. Ich bin sicher, dass viele sagen werden, dass diese Lösung unangemessen kompliziert und mit technischen Exzessen überladen ist. Vielleicht stimme ich dem zu.
Trotz der Tatsache, dass die Entwicklung dieses Projekts viel Zeit in Anspruch genommen hat, sind die Daten jetzt vollständig von der Präsentation getrennt. Wenn unsere Designer der Tabelle einige Zeilen hinzufügen oder vorhandene Zeilen daraus entfernen, muss ich den
verwirrenden HTML-Code nicht mehr verarbeiten. Dazu reicht es aus, mehrere Eigenschaften in der JSON-Datei zu ändern.
Lohnt sich das Ergebnis? Ich denke, dass es notwendig ist, die Umstände zu betrachten. Dies ist jedoch sehr charakteristisch für die Programmierung. Ich möchte sagen, dass in meinem Kopf während der Arbeit an diesem Projekt ständig das folgende Bild erschien.
Vielleicht ist dies die Antwort auf meine Frage, ob dieses Projekt den Aufwand für seine Entwicklung wert ist.
Liebe Leser! ? ?
