En el material, cuya traducción publicamos hoy, hablaremos sobre cómo crear una cuadrícula de impresión para el sistema de diseño utilizando las
funciones de renderización Vue .
Aquí hay una demostración del proyecto que vamos a revisar aquí.
Puedes encontrar su código aquí. El autor de este material dice que utilizó funciones de renderizado debido a que permiten un control mucho más preciso sobre el proceso de creación de código HTML que las plantillas Vue normales. Sin embargo, para su sorpresa, no pudo encontrar ejemplos prácticos de su aplicación. Solo encontró manuales de instrucciones. Espera que este material marque la diferencia para mejor gracias al ejemplo práctico del uso de las funciones de render de Vue.
Funciones de renderizado Vue
Las funciones de render siempre me parecieron algo inusual para Vue. Todo en este marco enfatiza el deseo de simplicidad y la separación de deberes de varias entidades. Pero las funciones de renderizado son una extraña mezcla de HTML y JavaScript, que a menudo es difícil de leer.
Por ejemplo, aquí está el marcado HTML:
<div class="container"> <p class="my-awesome-class">Some cool text</p> </div>
Para formarlo, necesita la siguiente función:
render(createElement) { return createElement("div", { class: "container" }, [ createElement("p", { class: "my-awesome-class" }, "Some cool text") ]) }
Sospecho que tales construcciones harán que muchos se alejen inmediatamente de las funciones de renderizado. Después de todo, la facilidad de uso es exactamente lo que atrae a los desarrolladores a Vue. Es una pena que muchas personas no vean sus verdaderos méritos detrás de la apariencia antiestética de las funciones de renderizado. La cuestión es que las funciones de render y los componentes funcionales son herramientas interesantes y poderosas. Yo, para demostrar sus capacidades y su verdadero valor, hablaré sobre cómo me ayudaron a resolver el problema real.
Tenga en cuenta que será muy útil abrir una
versión demo del proyecto en consideración en una pestaña del navegador adyacente y acceder a ella mientras lee un artículo.
Definición de criterios para un sistema de diseño.
Tenemos un sistema de diseño basado en
VuePress . Necesitábamos incluir una nueva página en ella, demostrando varias posibilidades tipográficas de diseño de texto. Así es como se veía el diseño que me dio el diseñador.
Diseño de páginaY aquí hay un ejemplo del código CSS correspondiente a esta página:
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; }
Los encabezados están formateados en función de los nombres de las etiquetas. Para formatear otros elementos, se utilizan nombres de clase. Además, hay clases separadas para riqueza y tamaños de fuente.
Antes de comenzar a escribir código, formulé algunas reglas:
Opciones para resolver el problema.
Antes de comenzar a trabajar, consideré varias opciones para resolver la tarea que tenía ante mí. Aquí está su descripción general.
▍ Escribir manualmente código HTML
Me gusta escribir código HTML manualmente, pero solo cuando nos permite resolver adecuadamente el problema existente. Sin embargo, en mi caso, la escritura manual de código significaría ingresar varios fragmentos de código repetidos en los que existen algunas variaciones. No me gustó Además, esto significaría que los datos no podrían almacenarse en un archivo separado. Como resultado, rechacé este enfoque.
Si crease la página en cuestión así, habría obtenido algo como lo siguiente:
<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>
▍Utilizando patrones Vue tradicionales
En condiciones normales, este enfoque se usa con mayor frecuencia. Sin embargo, eche un vistazo a
este ejemplo.
Un ejemplo de uso de plantillas VueEn la primera columna hay lo siguiente:
- La
<h1>
, que se presenta en la forma en que la muestra el navegador. - La
<p>
agrupa a varios <span>
hijos con texto. A cada uno de estos elementos se le asigna una clase (pero no se asigna ninguna clase especial a la etiqueta <p>
sí). - La
<p>
que no tiene elementos <span>
anidados a los que se asigna la clase.
Para implementar todo esto, se requerirían muchas instancias de las directivas
v-if
y
v-if-else
. Y esto, lo sé, llevaría al hecho de que el código se volvería muy confuso muy pronto. Además, no me gusta el uso de toda esta lógica condicional en el marcado.
▍Funciones de Render
Como resultado, después de analizar las posibles alternativas, seleccioné las funciones de render. En ellos, usando JavaScript, usando construcciones condicionales, se crean nodos secundarios de otros nodos. Al crear estos nodos secundarios, se tienen en cuenta todos los criterios necesarios. En esta situación, tal solución me pareció perfecta.
Modelo de datos
Como dije, quería almacenar datos tipográficos en un archivo JSON separado. Esto permitiría, si es necesario, realizar cambios en ellos sin tocar el marcado.
Estos son los datos.
Cada objeto JSON en el archivo es una descripción de una línea separada:
{ "text": "Heading 1", "element": "h1", // . "properties": "Balboa Light, 30px", // . "usage": ["Product title (once on a page)", "Illustration headline"] // . - . }
Aquí está el HTML que viene después de procesar este objeto:
<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>
Ahora considere un ejemplo más complejo. Las matrices representan grupos de niños. Las propiedades de los objetos de
classes
, que en sí mismos son objetos, pueden almacenar descripciones de clases. La propiedad
base
del objeto de
classes
contiene una descripción de las clases que son comunes a todos los nodos en la celda. Cada clase presente en la propiedad de
variants
se aplica a un único elemento en el grupo.
{ "text": "Body Text - Large", "element": "p", "classes": { "base": "body-text body-text--lg",
Este objeto se convierte en el siguiente HTML:
<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 estructura básica del proyecto.
Tenemos un componente principal,
TypographyTable.vue
, que contiene marcado para formar la tabla. También tenemos un componente secundario,
TypographyRow.vue
, que es responsable de crear la fila de la tabla y contiene nuestra función de representación.
Al formar filas de tabla, se atraviesa una matriz con datos. Los objetos que describen las filas de la tabla se pasan al componente
TypographyRow
como propiedades.
<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 {
Aquí me gustaría señalar una pequeña cosa agradable: los datos tipográficos en una instancia de Vue se pueden representar como una propiedad. Puede acceder a ellos utilizando la construcción
$options.typographyData
, ya que no cambian y no deberían ser reactivos (gracias a
Anton Kosykh ).
Crear un componente funcional
El componente
TypographyRow
que procesa datos es un componente funcional. Los componentes funcionales son entidades que no tienen estados ni instancias. Esto significa que no tienen
this
, y que no tienen acceso a los métodos del ciclo de vida del componente Vue.
Aquí está el "esqueleto" de un componente similar con el que comenzaremos a trabajar en nuestro componente:
El método de componente de
render
toma un argumento de
context
que tiene una propiedad de
props
. Esta propiedad está sujeta a la desestructuración y se utiliza como segundo argumento.
El primer argumento es
createElement
. Esta es una función que le dice a Vue qué nodo crear. En aras de la brevedad y la estandarización del código, uso la abreviatura
h
para
createElement
. Puedes leer sobre por qué hice esto
aquí .
Entonces
h
toma tres argumentos:
- Etiqueta HTML (por ejemplo,
div
). - Un objeto de datos que contiene atributos de plantilla (por ejemplo,
{ class: 'something'}
). - Cadenas de texto (si solo agregamos texto) o nodos secundarios creados con
h
.
Así es como se ve:
render(h, { props }) { return h("div", { class: "example-class" }, "Here's my example text") }
Resumamos lo que ya hemos creado. A saber, ahora tenemos lo siguiente:
- Un archivo con datos que se planea utilizar para formar la página.
- Un componente Vue normal que importa un archivo de datos.
- El marco del componente funcional que se encarga de mostrar las filas de la tabla.
Para crear filas de tabla, los datos del formato JSON se deben pasar como argumento a
h
. Puede transferir todos esos datos de una sola vez, pero con este enfoque necesitará una gran cantidad de lógica condicional, lo que degradará la comprensión del código. En cambio, decidí hacer esto:
- Transforme los datos a un formato estandarizado.
- Mostrar datos transformados.
Transformación de datos
Me gustaría que mis datos se presenten en un formato que coincida con los argumentos aceptados por
h
. Pero antes de convertirlos, planeé qué estructura deberían tener en el archivo JSON:
// { tag: "", // HTML- cellClass: "", // . - null text: "", // , children: [] // , . , . }
Cada objeto representa una celda de la tabla. Cuatro celdas forman cada fila de la tabla (se recopilan en una matriz):
// [ { cell1 }, { cell2 }, { cell3 }, { cell4 } ]
El punto de entrada puede ser una función como la siguiente:
function createRow(data) {
Miremos el diseño nuevamente.
Diseño de páginaPuede ver que en la primera columna los elementos tienen un estilo diferente. Y en las columnas restantes se usa el mismo formato. Entonces comencemos con esto.
Permítame recordarle que me gustaría usar la siguiente estructura JSON como modelo para describir cada celda:
{ tag: "", cellClass: "", text: "", children: [] }
Con este enfoque, se utilizará una estructura en forma de árbol para describir cada celda. Esto es precisamente porque algunas células contienen grupos de niños. Utilizamos las siguientes dos funciones para crear celdas:
- La función
createNode
toma cada una de las propiedades que nos interesan como argumento. - La función
createCell
desempeña el papel de un contenedor alrededor de createNode
, con su ayuda comprobamos si el text
del argumento text
matriz. Si es así, creamos una variedad de niños.
Ahora podemos hacer algo como esto:
function createRow(data) { let { text, element, classes = null, properties, usage } = data; let row = []; row[0] = "" row[1] = createCellData("p", ?????)
Al formar las columnas tercera y cuarta, pasamos
properties
y
usage
como argumentos de texto. Sin embargo, la segunda columna es diferente de la tercera y cuarta. Aquí mostramos los nombres de las clases, que, en los datos de origen, se almacenan de esta forma:
"classes": { "base": "body-text body-text--lg", "variants": ["body-text--bold", "body-text--regular"] },
Además, no olvidemos que cuando se trabaja con encabezados, las clases no se usan. Por lo tanto, necesitamos crear nombres de etiquetas de encabezado para las líneas correspondientes (es decir,
h1
,
h2
, etc.).
Creamos funciones auxiliares que nos permiten convertir estos datos en un formato que facilite su uso como argumento de
text
.
Ahora podemos hacer lo siguiente:
function createRow(data) { let { text, element, classes = null, properties, usage } = data; let row = []; row[0] = "" row[1] = createCellData("p", displayClasses(element, classes))
Transformación de datos utilizados para demostrar estilos.
Necesitamos decidir qué hacer con la primera columna de la tabla, que muestra ejemplos de la aplicación de estilos. Esta columna es diferente de las demás. Aquí aplicamos nuevas etiquetas y clases a cada celda en lugar de usar la combinación de clases utilizada por las columnas restantes:
<p class="body-text body-text--md body-text--semibold">
En lugar de intentar implementar esta funcionalidad en
createCellData
o
createNodeData
, propongo crear una nueva función que aproveche las capacidades de estas funciones básicas que realizan la transformación de datos. Implementará un nuevo mecanismo de procesamiento de datos:
function createDemoCellData(data) { let children; const classes = getClasses(data.classes);
Ahora los datos de la cadena se reducen a un formato normalizado y se pueden pasar a la función de representación:
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 }
Procesamiento de datos
Aquí se explica cómo representar los datos que se muestran en la página:
¡Ahora
todo está listo !
Aquí , nuevamente, el código fuente.
Resumen
Vale la pena decir que el enfoque considerado aquí es una forma experimental de resolver un problema bastante trivial. Estoy seguro de que muchos dirán que esta solución es excesivamente complicada y está sobrecargada con excesos de ingeniería. Quizás esté de acuerdo con eso.
A pesar de que el desarrollo de este proyecto tomó mucho tiempo, los datos ahora están completamente separados de la presentación. Ahora, si nuestros diseñadores entran para agregar algunas filas a la tabla, o eliminar cualquier fila existente de ella, no tengo que rastrillar el código HTML
confuso . Para hacer esto, será suficiente para mí cambiar varias propiedades en el archivo JSON.
¿Vale la pena el resultado? Creo que es necesario mirar las circunstancias. Esto, sin embargo, es muy característico de la programación. Quiero decir que en mi cabeza, en el proceso de trabajar en este proyecto, apareció constantemente la siguiente imagen.
Quizás esta sea la respuesta a mi pregunta sobre si este proyecto vale la pena el esfuerzo dedicado a su desarrollo.
Estimados lectores! ? ?
