普通视图

发现新文章,点击刷新页面。
昨天 — 2025年7月25日首页

框架实战指南-透明元素

作者 _未完待续
2025年7月25日 18:26

本文是系列文章的一部分:框架实战指南 - 基础知识

呼!上一章太精彩了。这一章我们放慢节奏吧:短小精悍。

让我们回想一下“动态 HTML”“组件简介”章节,我们在其中构建了我们的File FileList组件:

<!-- File.vue --><script setup>import { ref, onMounted, onUnmounted } from "vue";import FileDate from "./FileDate.vue";const props = defineProps(["isSelected", "isFolder", "fileName", "href"]);const emit = defineEmits(["selected"]);const inputDate = ref(new Date());// ...</script><template><buttonv-on:click="emit('selected')":style="isSelected? 'background-color: blue; color: white': 'background-color: white; color: blue'">{{ fileName }}<span v-if="isFolder">Type: Folder</span><span v-else>Type: File</span><FileDate v-if="!isFolder" :inputDate="inputDate" /></button></template>
<!-- FileList.vue --><script setup>// ...</script><template><!-- ... --><ul><li v-for="(file, i) in filesArray" :key="file.id"><Filev-if="onlyShowFiles ? !file.isFolder : true"@selected="onSelected(i)":isSelected="selectedIndex === i":fileName="file.fileName":href="file.href":isFolder="file.isFolder"/></li></ul><!-- ... --></template>

虽然理论上可行onlyShowFiles=true,但存在一个重大问题。让我们看一下使用以下代码渲染时的 HTML 效果filesArray

[{fileName: "File one",href: "/file/file_one",isFolder: false,id: 1,},{fileName: "Folder one",href: "",isFolder: true,id: 2,},];

因为我们的条件语句是在li渲染到 DOM 时,所以它可能看起来像这样:

<!-- ... --><ul><li><!-- File Component --><button>...</button></li><li></li></ul><!-- ... -->

虽然乍一看这似乎不是什么大问题,但事实上li我们的中间有一个空白ul,这带来了三个问题:

  1. 它将留下由您对其应用的任何样式所创建的空白空间li
  2. 任何辅助技术,例如屏幕阅读器,都会读出有一个空项目,这对于这些用户来说是一种令人困惑的行为。
  3. 任何从您的页面读取数据的搜索引擎都可能错误地认为您的列表是故意空的,从而可能影响您在网站上的排名。

解决这些问题需要“透明元素”的帮助。理想情况下,我们想要的是类似标签那样的东西,它渲染后什么也不显示。

考虑支持

这意味着如果我们可以在框架代码中生成类似以下伪语法的内容:

<ul><nothing><li><button>...</button></li></nothing><nothing></nothing></ul>

我们可以将其渲染到 DOM 本身中:

<ul><li><button>...</button></li></ul>

幸运的是,这三个框架都提供了实现这一点的方法,只是语法不同。让我们看看每个框架是如何实现的:

为了呈现类似于nothing元素的东西,我们可以使用template带有v-forv-if与之关联的元素。

<template><ul><template v-for="(file, i) of filesArray" :key="file.id"><li v-if="onlyShowFiles ? !file.isFolder : true"><File@selected="onSelected(i)":isSelected="selectedIndex === i":fileName="file.fileName":href="file.href":isFolder="file.isFolder"/></li></template></ul></template>

堆叠透明元素

需要简单说明的是,这些元素不仅可以nothing使用一次,而且可以连续堆叠在一起来做……嗯,没什么!

以下是一些呈现以下内容的代码示例:

<p>Test</p>

虽然其他框架与我们的伪语法有更 1:1 的映射nothing,但 Vue 由于重用了现有的 HTML<template>标签,因此采用了略有不同的方法。

默认情况下,如果您在 Vue 中除根之外的任何其他位置渲染template,它将不会在屏幕上渲染任何内容:

<template><template><p>Test</p></template></template>

值得一提的是,即使屏幕上什么都不显示,该template元素仍然位于 DOM 本身中,等待以其他方式使用。虽然解释 HTMLtemplate元素默认不渲染“什么”超出了本书的讨论范围,但这是预期的行为。

但是,如果添加v-forv-if或(我们将在“访问子项”一章v-slot介绍什么是),它将删除并仅渲染子项。v-slot<template>

这意味着:

<template><template v-if="true"><p>Test</p></template></template>

和:

<template><template v-if="true"><template v-if="true"><template v-if="true"><p>Test</p></template></template></template></template>

都将呈现以下 HTML:

<p>Test</p>

当然,这些规则不适用于根级template,它充当模板代码的容器。一开始可能会有点困惑,但多加练习就会明白。

挑战

现在我们了解了如何渲染透明元素(无论如何对 DOM 透明),让我们构建一个有用的例子。

也就是说,假设我们想要构建一个按钮栏,按钮之间有间隙:

image.png

要使用 HTML 执行此操作,我们可能有以下模板和样式:

<divstyle="    display: 'inline-flex',gap: 1rem;  "><button>Delete</button><button>Copy</button><button>Favorite</button><button>Settings</button></div>

但是,如果我们只想显示前三个按钮该怎么办?

  • 删除
  • 复制
  • 最喜欢的

仅当选择文件时?

让我们使用我们最喜欢的框架来构建它:

<!-- FileActionButtons.vue --><script setup>const emit = defineEmits(["delete", "copy", "favorite"]);</script><template><div><button @click="emit('delete')">Delete</button><button @click="emit('copy')">Copy</button><button @click="emit('favorite')">Favorite</button></div></template>
<!-- ButtonBar.vue --><script setup>import FileActionButtons from "./FileActionButtons.vue";const props = defineProps(["fileSelected"]);const emit = defineEmits(["delete", "copy", "favorite", "settings"]);</script><template><div style="display: flex; gap: 1rem"><FileActionButtonsv-if="props.fileSelected"@delete="emit('delete')"@copy="emit('copy')"@favorite="emit('favorite')"/><button @click="emit('settings')">Settings</button></div></template>

糟糕!渲染结果和我们预期的不一样!

image.png

div这是因为当我们在组件中使用 时FileActionButtons,它绕过了gapCSS 的属性。为了解决这个问题,我们可以使用方便的nothing元素:

因为 Vue 的根<template>可以支持多个元素,而不需要v-ifv-forv-slot,所以我们可以执行以下操作:

<!-- FileActionButtons.vue --><template><button @click="emit('delete')">Delete</button><button @click="emit('copy')">Copy</button><button @click="emit('favorite')">Favorite</button></template><!-- ... -->
❌
❌