No material, cuja tradução estamos publicando hoje, falaremos sobre como criar uma grade de impressão para o sistema de design usando as
funções de renderização do
Vue .
Aqui está uma demonstração do projeto que vamos revisar aqui.
Você pode encontrar seu código aqui. O autor deste material diz que ele usou funções de renderização porque elas permitem um controle muito mais preciso sobre o processo de criação de código HTML do que os modelos regulares do Vue. No entanto, para sua surpresa, ele não conseguiu encontrar exemplos práticos de sua aplicação. Ele encontrou apenas manuais de instruções. Ele espera que este material faça a diferença para melhor, graças ao exemplo prático de uso das funções de renderização do Vue aqui.
Funções de renderização do Vue
As funções de renderização sempre me pareciam algo incomum para o Vue. Tudo nessa estrutura enfatiza o desejo de simplicidade e a separação de deveres de várias entidades. Mas as funções de renderização são uma estranha mistura de HTML e JavaScript, que geralmente é difícil de ler.
Por exemplo, aqui está a marcação HTML:
<div class="container"> <p class="my-awesome-class">Some cool text</p> </div>
Para formar, você precisa da seguinte função:
render(createElement) { return createElement("div", { class: "container" }, [ createElement("p", { class: "my-awesome-class" }, "Some cool text") ]) }
Suspeito que tais construções façam com que muitos se afastem imediatamente das funções de renderização. Afinal, a facilidade de uso é exatamente o que atrai os desenvolvedores ao Vue. É uma pena que muitas pessoas não vejam seus verdadeiros méritos por trás da aparência desagradável das funções de renderização. O fato é que funções de renderização e componentes funcionais são ferramentas interessantes e poderosas. Eu, para demonstrar suas capacidades e seu verdadeiro valor, falarei sobre como eles me ajudaram a resolver o problema real.
Observe que será muito útil abrir uma
versão demo do projeto em consideração na guia do navegador vizinho e acessá-la enquanto lê o artigo.
Definindo critérios para um sistema de design
Temos um sistema de design baseado no
VuePress . Precisávamos incluir uma nova página, demonstrando várias possibilidades tipográficas do design do texto. Foi assim que o layout que o designer me deu parecia.
Layout da páginaE aqui está um exemplo do código CSS correspondente 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; }
Os cabeçalhos são formatados com base nos nomes das tags. Para formatar outros elementos, nomes de classe são usados. Além disso, existem classes separadas para riqueza e tamanhos de fonte.
Antes de começar a escrever o código, formulei algumas regras:
Opções para resolver o problema
Antes de começar o trabalho, considerei várias opções para resolver a tarefa definida diante de mim. Aqui está a visão geral deles.
WritingManualmente escrevendo código HTML
Eu gosto de escrever código HTML manualmente, mas somente quando ele nos permite resolver adequadamente o problema existente. Entretanto, no meu caso, a escrita manual de código significaria inserir vários fragmentos de código repetidos nos quais algumas variações estão presentes. Eu não gostei Além disso, isso significa que os dados não podem ser armazenados em um arquivo separado. Como resultado, recusei essa abordagem.
Se eu tivesse criado a página em questão dessa maneira, teria algo como o seguinte:
<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>
SingUsando padrões tradicionais do Vue
Sob condições normais, essa abordagem é usada com mais frequência. No entanto, dê uma olhada
neste exemplo.
Um exemplo de uso de modelos do VueNa primeira coluna, há o seguinte:
- A
<h1>
, apresentada na forma em que o navegador a exibe. - A
<p>
agrupa vários filhos <span>
com texto. A cada um desses elementos é atribuída uma classe (mas nenhuma classe especial é atribuída à própria tag <p>
). - A
<p>
que não possui elementos <span>
aninhados aos quais a classe está atribuída.
Para implementar tudo isso, muitas instâncias das diretivas
v-if
e
v-if-else
seriam necessárias. E isso, eu sei, levaria ao fato de que o código se tornaria muito confuso muito em breve. Além disso, não gosto do uso de toda essa lógica condicional na marcação.
Funções de renderização
Como resultado, depois de analisar as possíveis alternativas, selecionei as funções de renderização. Neles, usando JavaScript, usando construções condicionais, nós filhos de outros nós são criados. Ao criar esses nós filhos, todos os critérios necessários são levados em consideração. Nessa situação, essa solução parecia perfeita para mim.
Modelo de dados
Como eu disse, queria armazenar dados tipográficos em um arquivo JSON separado. Isso permitiria, se necessário, fazer alterações neles sem tocar na marcação.
Estes são os dados.
Cada objeto JSON no arquivo é uma descrição de uma linha separada:
{ "text": "Heading 1", "element": "h1", // . "properties": "Balboa Light, 30px", // . "usage": ["Product title (once on a page)", "Illustration headline"] // . - . }
Aqui está o HTML que vem após o processamento deste 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>
Agora considere um exemplo mais complexo. Matrizes representam grupos de crianças. As propriedades dos objetos de
classes
, que são objetos, podem armazenar descrições de classe. A propriedade
base
do objeto de
classes
contém uma descrição das classes comuns a todos os nós na célula. Cada classe presente na propriedade
variants
é aplicada a um único elemento no grupo.
{ "text": "Body Text - Large", "element": "p", "classes": { "base": "body-text body-text--lg",
Este objeto se transforma no seguinte 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>
A estrutura básica do projeto
Temos um componente pai,
TypographyTable.vue
, que contém marcação para formar a tabela. Também temos um componente filho,
TypographyRow.vue
, responsável pela criação da linha da tabela e contém nossa função de renderização.
Ao formar linhas de tabela, uma matriz com dados é percorrida. Os objetos que descrevem as linhas da tabela são passados para o componente
TypographyRow
como propriedades.
<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 {
Aqui, gostaria de observar uma ninharia agradável: os dados tipográficos em uma instância do Vue podem ser representados como uma propriedade. Você pode acessá-los usando a construção
$options.typographyData
, pois eles não mudam e não devem ser reativos (graças a
Anton Kosykh ).
Criando um componente funcional
O componente
TypographyRow
que processa dados é um componente funcional. Componentes funcionais são entidades que não possuem estados e instâncias. Isso significa que eles não têm
this
e que não têm acesso aos métodos de ciclo de vida do componente Vue.
Aqui está o "esqueleto" de um componente semelhante com o qual começaremos a trabalhar em nosso componente:
O método do componente de
render
usa um argumento de
context
que possui uma propriedade
props
. Esta propriedade está sujeita a desestruturação e é usada como o segundo argumento.
O primeiro argumento é
createElement
. Essa é uma função que informa ao Vue qual nó criar. Por uma questão de brevidade e padronização do código, eu uso a abreviação
h
para
createElement
. Você pode ler sobre por que eu fiz isso
aqui .
Então
h
leva três argumentos:
- Tag HTML (por exemplo,
div
). - Um objeto de dados contendo atributos de modelo (por exemplo,
{ class: 'something'}
). - Sequências de texto (se adicionarmos apenas texto) ou nós filhos criados usando
h
.
Aqui está o que parece:
render(h, { props }) { return h("div", { class: "example-class" }, "Here's my example text") }
Vamos resumir o que já criamos. Nomeadamente, agora temos o seguinte:
- Um arquivo com dados planejados para serem usados na formação da página.
- Um componente regular do Vue que importa um arquivo de dados.
- A estrutura do componente funcional que é responsável por exibir as linhas da tabela.
Para criar linhas da tabela, os dados do formato JSON devem ser passados como argumento para
h
. Você pode transferir todos esses dados de uma só vez, mas com essa abordagem, você precisará de uma grande quantidade de lógica condicional, o que degradará a compreensibilidade do código. Em vez disso, decidi fazer o seguinte:
- Transforme dados em um formato padronizado.
- Exibir dados transformados.
Transformação de dados
Gostaria que meus dados fossem apresentados em um formato que corresponda aos argumentos aceitos por
h
. Mas antes de convertê-los, planejei qual estrutura eles deveriam ter no arquivo JSON:
// { tag: "", // HTML- cellClass: "", // . - null text: "", // , children: [] // , . , . }
Cada objeto representa uma célula da tabela. Quatro células formam cada linha da tabela (elas são coletadas em uma matriz):
// [ { cell1 }, { cell2 }, { cell3 }, { cell4 } ]
O ponto de entrada pode ser uma função como a seguinte:
function createRow(data) {
Vamos olhar para o layout novamente.
Layout da páginaVocê pode ver que na primeira coluna os elementos são estilizados de maneira diferente. E nas colunas restantes a mesma formatação é usada. Então, vamos começar com isso.
Deixe-me lembrá-lo de que gostaria de usar a seguinte estrutura JSON como modelo para descrever cada célula:
{ tag: "", cellClass: "", text: "", children: [] }
Com essa abordagem, uma estrutura semelhante a uma árvore será usada para descrever cada célula. Isto é precisamente porque algumas células contêm grupos de crianças. Usamos as duas funções a seguir para criar células:
- A função
createNode
usa cada uma das propriedades em que estamos interessados como argumento. - A função
createCell
desempenha o papel de um wrapper em torno do createNode
, com sua ajuda, verificamos se o text
do argumento text
matriz. Nesse caso, criamos uma matriz de filhos.
Agora podemos fazer algo assim:
function createRow(data) { let { text, element, classes = null, properties, usage } = data; let row = []; row[0] = "" row[1] = createCellData("p", ?????)
Ao formar a terceira e quarta colunas, passamos
properties
e
usage
como argumentos de texto. No entanto, a segunda coluna é diferente da terceira e quarta. Aqui, exibimos os nomes das classes, que, nos dados de origem, são armazenadas neste formato:
"classes": { "base": "body-text body-text--lg", "variants": ["body-text--bold", "body-text--regular"] },
Além disso, não devemos esquecer que, ao trabalhar com cabeçalhos, as classes não são usadas. Portanto, precisamos criar nomes de tags de cabeçalho para as linhas correspondentes (ou seja,
h1
,
h2
e assim por diante).
Criamos funções auxiliares que nos permitem converter esses dados em um formato que facilita seu uso como argumento de
text
.
Agora podemos fazer o seguinte:
function createRow(data) { let { text, element, classes = null, properties, usage } = data; let row = []; row[0] = "" row[1] = createCellData("p", displayClasses(element, classes))
Transformação de dados usados para demonstrar estilos
Precisamos decidir o que fazer com a primeira coluna da tabela, mostrando exemplos da aplicação de estilos. Esta coluna é diferente das outras. Aqui, aplicamos novas tags e classes a cada célula, em vez de usar a combinação de classes usada pelas colunas restantes:
<p class="body-text body-text--md body-text--semibold">
Em vez de tentar implementar essa funcionalidade em
createCellData
ou
createNodeData
, proponho a criação de uma nova função que aproveitará os recursos dessas funções básicas que executam a transformação de dados. Ele implementará um novo mecanismo de processamento de dados:
function createDemoCellData(data) { let children; const classes = getClasses(data.classes);
Agora, os dados da string são reduzidos para um formato normalizado e podem ser passados para a função de renderização:
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 }
Renderização de dados
Veja como renderizar os dados exibidos na página:
Agora
está tudo pronto !
Aqui , novamente, o código fonte.
Sumário
Vale dizer que a abordagem aqui considerada é uma maneira experimental de resolver um problema bastante trivial. Estou certo de que muitos dirão que esta solução é excessivamente complicada e sobrecarregada com excessos de engenharia. Talvez eu concorde com isso.
Apesar de o desenvolvimento deste projeto ter demorado muito, os dados agora estão completamente separados da apresentação. Agora, se nossos designers entrarem para adicionar algumas linhas à tabela ou remover quaisquer linhas existentes dela, não preciso usar o código HTML
confuso . Para fazer isso, basta alterar várias propriedades no arquivo JSON.
O resultado vale o esforço? Eu acho que é necessário olhar para as circunstâncias. Isso, no entanto, é muito característico da programação. Quero dizer que, na minha cabeça, no processo de trabalhar neste projeto, a figura a seguir apareceu constantemente.
Talvez essa seja a resposta para minha pergunta sobre se esse projeto vale o esforço despendido em seu desenvolvimento.
Caros leitores! ? ?
