在我们今天发布的翻译材料中,我们将讨论如何使用
Vue渲染功能为设计系统创建打印网格。
这是项目
的演示,我们将在这里进行审查。
您可以在此处找到其代码。 该材料的作者说他之所以使用渲染功能,是因为与常规的Vue模板相比,它们可以更精确地控制创建HTML代码的过程。 但是,令他惊讶的是,他找不到实际应用的实例。 他只遇到了使用说明书。 他希望通过使用Vue渲染功能的实际示例,使这种材料能够更好地发挥作用。
Vue渲染功能
在我看来,渲染功能始终对Vue来说有点不寻常。 此框架中的所有内容都强调了对简单性和各个实体职责分离的渴望。 但是渲染功能是HTML和JavaScript的奇怪结合,通常很难阅读。
例如,这是HTML标记:
<div class="container"> <p class="my-awesome-class">Some cool text</p> </div>
要形成它,您需要以下功能:
render(createElement) { return createElement("div", { class: "container" }, [ createElement("p", { class: "my-awesome-class" }, "Some cool text") ]) }
我怀疑这样的构造会使许多人立即放弃渲染功能。 毕竟,易用性正是吸引开发人员使用Vue的原因。 如果许多人没有看到渲染功能难看的外观背后的真实价值,这是一个遗憾。 问题是渲染功能和功能组件是有趣且功能强大的工具。 为了证明他们的能力和真正的价值,我将谈论他们如何帮助我解决实际问题。
请注意,在相邻的浏览器选项卡中打开正在考虑的项目的
演示版本,并在阅读文章时对其进行访问将非常有用。
定义设计系统的标准
我们有一个基于
VuePress的设计系统。 我们需要在其中添加一个新页面,以演示文本设计的各种印刷可能性。 这就是设计师给我的布局外观。
页面布局以下是与此页面对应的CSS代码的示例:
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; }
标头是根据标签名称格式化的。 要格式化其他元素,请使用类名称。 此外,还有用于丰富性和字体大小的单独类。
在开始编写代码之前,我制定了一些规则:
解决问题的选项
在开始工作之前,我考虑了几种解决摆在我面前的任务的选项。 这是他们的概述。
▍手动编写HTML代码
我喜欢手动编写HTML代码,但前提是它可以让我们充分解决现有问题。 但是,在我的情况下,手动编写代码意味着要输入各种重复的代码片段,其中存在一些变化。 我不喜欢 此外,这意味着无法将数据存储在单独的文件中。 结果,我拒绝了这种方法。
如果我像这样创建有问题的页面,我将得到以下内容:
<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>
traditional使用传统的Vue模式
在正常情况下,这种方法最常用。 但是,看看
这个例子。
使用Vue模板的示例在第一列中有以下内容:
<h1>
,以浏览器显示它的形式显示。<p>
将多个<span>
子级与文本分组。 为这些元素中的每个元素分配了一个类(但没有为<p>
标记本身分配任何特殊的类)。<p>
,该<p>
没有嵌套该类的<span>
元素。
要实现所有这些,将需要
v-if
和
v-if-else
指令的许多实例。 我知道,这将导致这样的事实,即代码很快就会变得非常混乱。 另外,我不喜欢在标记中使用所有这些条件逻辑。
▍渲染功能
结果,在分析了可能的替代方法之后,我选择了渲染函数。 在它们中,使用JavaScript,使用条件构造来创建其他节点的子节点。 创建这些子节点时,将考虑所有必要条件。 在这种情况下,这种解决方案对我来说似乎很完美。
资料模型
正如我所说,我想将印刷数据存储在一个单独的JSON文件中。 如有必要,这将允许在不更改标记的情况下对其进行更改。
这些是数据。
文件中的每个JSON对象都是单独一行的描述:
{ "text": "Heading 1", "element": "h1", // . "properties": "Balboa Light, 30px", // . "usage": ["Product title (once on a page)", "Illustration headline"] // . - . }
这是处理此对象后出现的HTML:
<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>
现在考虑一个更复杂的示例。 数组代表子组。
classes
对象的属性本身就是对象,可以存储类描述。
classes
对象的
base
属性包含对单元中所有节点通用的类的描述。
variants
属性中存在的每个类都应用于组中的单个元素。
{ "text": "Body Text - Large", "element": "p", "classes": { "base": "body-text body-text--lg",
该对象变成以下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>
项目的基本结构
我们有一个父组件
TypographyTable.vue
,其中包含用于形成表格的标记。 我们还有一个子组件
TypographyRow.vue
,它负责创建表行并包含我们的render函数。
形成表行时,将遍历带有数据的数组。 描述表行的对象作为属性传递给
TypographyRow
组件。
<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 {
在这里,我要指出一个令人愉快的地方:Vue实例中的印刷数据可以表示为属性。 您可以使用
$options.typographyData
构造访问它们,因为它们不会更改并且不应是被动的(感谢
Anton Kosykh )。
创建功能组件
处理数据的
TypographyRow
组件是功能组件。 功能组件是没有状态和实例的实体。 这意味着他们没有
this
,并且他们无权访问Vue组件生命周期方法。
这是相似组件的“骨架”,我们将开始使用该组件进行工作:
render
组件方法采用具有
props
属性的
context
参数。 此属性可以进行解构,并用作第二个参数。
第一个参数是
createElement
。 这是一个告诉Vue创建节点的功能。 为了代码的简洁和标准化,我使用
createElement
的简写形式。 您可以
在这里阅读有关我为什么这样做的
信息 。
因此,
h
需要三个参数:
- HTML标记(例如
div
)。 - 包含模板属性的数据对象(例如
{ class: 'something'}
)。 - 文本字符串(如果我们仅添加文本)或使用
h
创建的子节点。
看起来是这样的:
render(h, { props }) { return h("div", { class: "example-class" }, "Here's my example text") }
让我们总结一下我们已经创建的内容。 即,我们现在有以下内容:
- 文件,该文件包含计划在页面结构中使用的数据。
- 导入数据文件的常规Vue组件。
- 负责显示表中各行的功能组件的框架。
要创建表行,必须将JSON格式的数据作为参数传递给
h
。 您可以一次性传输所有此类数据,但是使用这种方法,您将需要大量的条件逻辑,这将降低代码的可理解性。 相反,我决定这样做:
- 将数据转换为标准格式。
- 显示转换后的数据。
数据转换
我希望以与
h
接受的参数匹配的格式显示数据。 但是在转换它们之前,我计划了它们在JSON文件中应具有的结构:
// { tag: "", // HTML- cellClass: "", // . - null text: "", // , children: [] // , . , . }
每个对象代表表的一个单元格。 表格的每一行由四个单元格组成(它们收集在一个数组中):
// [ { cell1 }, { cell2 }, { cell3 }, { cell4 } ]
输入点可能是如下函数:
function createRow(data) {
让我们再次看一下布局。
页面布局您可以在第一列中看到元素的样式不同。 在其余的列中,使用相同的格式。 因此,让我们从此开始。
让我提醒您,我想使用以下JSON结构作为描述每个单元格的模型:
{ tag: "", cellClass: "", text: "", children: [] }
通过这种方法,树状结构将用于描述每个单元。 正是因为某些单元格包含子组。 我们使用以下两个函数来创建单元格:
createNode
函数将我们感兴趣的每个属性用作参数。createCell
函数充当createNode
的包装器,借助它的帮助,我们可以检查参数text
数组。 如果是这样,我们将创建一系列子代。
现在我们可以做这样的事情:
function createRow(data) { let { text, element, classes = null, properties, usage } = data; let row = []; row[0] = "" row[1] = createCellData("p", ?????)
在形成第三和第四列时,我们将
properties
和
usage
作为文本参数传递。 但是,第二列不同于第三列和第四列。 在这里,我们显示类的名称,这些名称在源数据中以这种形式存储:
"classes": { "base": "body-text body-text--lg", "variants": ["body-text--bold", "body-text--regular"] },
另外,我们不要忘记在使用标头时不使用类。 因此,我们需要为相应的行(例如
h1
,
h2
等)创建标题标签名称。
我们创建辅助功能,使我们能够将此数据转换为便于将其用作
text
参数的格式。
现在,我们可以执行以下操作:
function createRow(data) { let { text, element, classes = null, properties, usage } = data; let row = []; row[0] = "" row[1] = createCellData("p", displayClasses(element, classes))
用于演示样式的数据转换
我们需要决定如何处理表格的第一列,以显示样式应用程序的示例。 此列与其他列不同。 在这里,我们将新的标记和类应用于每个单元,而不是使用其余列使用的类的组合:
<p class="body-text body-text--md body-text--semibold">
我建议
createCellData
创建一个新功能,以利用这些执行数据转换的基本功能的功能,而不是尝试在
createCellData
或
createNodeData
实现此功能。 它将实现新的数据处理机制:
function createDemoCellData(data) { let children; const classes = getClasses(data.classes);
现在,字符串数据被简化为规范化格式,并且可以传递给render函数:
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 }
数据渲染
以下是呈现页面上显示的数据的方法:
现在
一切就绪 !
还是这里的源代码。
总结
值得一提的是,这里考虑的方法是解决一个相当琐碎的问题的实验方法。 我敢肯定,很多人会说这种解决方案是不合理的复杂,并且由于工程过剩而过载。 也许我会同意这一点。
尽管该项目的开发花费了很多时间,但现在数据与演示完全分开了。 现在,如果我们的设计师进来在表中添加一些行,或从表中删除任何现有的行,那么我就不必弄乱
令人困惑的 HTML代码。 为此,对于我来说更改JSON文件中的几个属性就足够了。
结果值得付出努力吗? 我认为有必要看一下情况。 但是,这是编程的特征。 我想说的是,在我从事这个项目的过程中,以下图片不断出现。
也许这是我关于这个项目是否值得花在其开发上的努力的答案。
亲爱的读者们!您可以对此处讨论的项目表达什么想法和建议?您以什么方式解决了此类问题?