作者选择 Open Sourcing Mental Illness 作为 Write for DOnations 计划的一部分来接受捐赠。
介绍
使用 Vue.js 创建 Web 应用程序时,最佳实践是用小的模块化代码块构建应用程序。 这不仅使您的应用程序的各个部分保持专注,而且随着应用程序的复杂性增加,它也使应用程序更容易更新。 由于从 Vue CLI 生成的应用程序需要构建步骤,因此您可以访问 单文件组件 (SFC) 以将模块化引入您的应用程序。 SFC 具有 .vue 扩展名并包含 HTML <template>、<script> 和 <style> 标记,并且可以在其他组件中实现。
SFC 为开发人员提供了一种为每个组件创建自己的 HTML 标签,然后在他们的应用程序中使用它们的方法。 与 <p> HTML 标签将在浏览器中渲染段落并保持未渲染功能的方式相同,组件标签将渲染 SFC,无论它放置在 Vue 模板中的任何位置。
在本教程中,您将创建一个 SFC 并使用 props 向下传递数据,并使用 slots 在标签之间注入内容。 在本教程结束时,您将对什么是 SFC 以及如何处理代码可重用性有一个大致的了解。
先决条件
- Node.js 版本
14.16.0或更高版本安装在您的计算机上。 要在 macOS 或 Ubuntu 20.04 上安装它,请按照 如何在 macOS 上安装 Node.js 和创建本地开发环境中的步骤或 的 使用 PPA 部分安装如何在 Ubuntu 20.04 上安装 Node.js - Vue CLI 在您的机器上安装 并生成了一个新项目。 由于本教程使用 Vue 3 Composition API,因此请确保在生成应用程序时选择
3.x (Preview)选项。 该项目的名称将是sfc-project,它将作为根目录。 - 您还需要 JavaScript、HTML 和 CSS 的基本知识,您可以在我们的 如何使用 HTML 构建网站系列、如何使用 CSS 构建网站 系列中找到这些知识, 以及 如何在 JavaScript 中编码 。
第 1 步 — 设置项目
在本教程中,您将创建一个机场卡片组件,该组件在一系列卡片中显示多个机场及其代码。 完成先决条件部分后,您将拥有一个名为 sfc-project 的新 Vue 项目。 在本节中,您将把数据导入到这个生成的应用程序中。 此数据将是一个对象数组,其中包含一些您将用于在浏览器中显示信息的属性。
生成项目后,打开终端和 cd 或将目录更改为根 src 文件夹:
cd sfc-project/src
从那里,使用 mkdir 命令创建一个名为 data 的新目录,然后使用 touch 命令创建一个名为 us-airports.js 的新文件:
mkdir data touch data/us-airports.js
在您选择的文本编辑器中,打开这个新的 JavaScript 文件并添加以下本地数据:
sfc-项目/数据/us-airports.js
export default [
{
name: 'Cincinnati/Northern Kentucky International Airport',
abbreviation: 'CVG',
city: 'Hebron',
state: 'KY'
},
{
name: 'Seattle-Tacoma International Airport',
abbreviation: 'SEA',
city: 'Seattle',
state: 'WA'
},
{
name: 'Minneapolis-Saint Paul International Airport',
abbreviation: 'MSP',
city: 'Bloomington',
state: 'MN'
}
]
该数据是由美国的几个机场组成的 objects 的 array。 这将在本教程后面的单文件组件中呈现。
保存并退出文件。
接下来,您将创建另一组机场数据。 该数据将包括欧洲机场。 使用 touch 命令,创建一个名为 eu-airports.js 的新 JavaScript 文件:
touch data/eu-airports.js
然后打开文件并添加以下数据:
sfc-项目/数据/eu-airports.js
export default [
{
name: 'Paris-Charles de Gaulle Airport',
abbreviation: 'CDG',
city: 'Paris',
state: 'Ile de France'
},
{
name: 'Flughafen München',
abbreviation: 'MUC',
city: 'Munich',
state: 'Bavaria'
},
{
name: 'Fiumicino "Leonardo da Vinci" International Airport',
abbreviation: 'FCO',
city: 'Rome',
state: 'Lazio'
}
]
这组数据分别适用于法国、德国和意大利的欧洲机场。
保存并退出文件。
接下来,在根目录中,在终端中运行以下命令以启动在本地开发服务器上运行的 Vue CLI 应用程序:
npm run serve
这将在浏览器中的 localhost:8080 上打开应用程序。 您机器上的端口号可能不同。
访问浏览器中的地址。 您将找到以下启动屏幕:
接下来,启动一个新终端并打开 src 文件夹中的 App.vue 文件。 在此文件中,删除 <template> 中的 img 和 HelloWorld 标记以及 <script>。 您的 App.vue 将类似于以下内容:
sfc-项目/src/App.vue
<template>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
在此之后,导入您之前创建的 us-airports.js 文件。 为了使该数据具有反应性,以便您可以在 <template> 中使用它,您需要从 vue 导入 ref 函数。 您需要返回 airport 引用,以便可以在 HTML 模板中使用数据。
添加以下突出显示的行:
sfc-项目/src/App.vue
<template>
<div class="wrapper">
<div v-for="airport in airports" :key="airport.abbreviation" class="card">
<p>{{ airport.abbreviation }}</p>
<p>{{ airport.name }}</p>
<p>{{ airport.city }}, {{ airport.state }}</p>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import data from '@/data/us-airports.js'
export default {
name: 'App',
setup() {
const airports = ref(data)
return { airports }
}
}
</script>
...
在此代码段中,您导入了数据并使用模板中的 <div> 元素和 v-for 指令 对其进行了渲染。
至此,数据已导入并准备好在 App.vue 组件中使用。 但首先,添加一些样式以使数据更易于用户阅读。 在同一个文件中,在 <style> 标记中添加以下 CSS:
sfc-项目/src/App.vue
...
<style>
#app { ... }
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-column-gap: 1rem;
max-width: 960px;
margin: 0 auto;
}
.card {
border: 3px solid;
border-radius: .5rem;
padding: 1rem;
margin-bottom: 1rem;
}
.card p:first-child {
font-weight: bold;
font-size: 2.5rem;
margin: 1rem 0;
}
.card p:last-child {
font-style: italic;
font-size: .8rem;
}
</style>
在这种情况下,您使用 CSS Grid 将这些机场代码卡片组合成三个网格。 注意这个网格是如何在 .wrapper 类中设置的。 .card 类是包含每个机场代码、名称和位置的卡片或部分。 如果您想了解有关 CSS 的更多信息,请查看我们的 如何使用 CSS 设置 HTML 样式。
打开浏览器并导航到 localhost:8080。 您会发现许多带有机场代码和信息的卡片:
现在您已经设置了初始应用程序,您可以在下一步中将数据重构为单文件组件。
第 2 步 — 创建单文件组件
由于 Vue CLI 使用 Webpack 将您的应用程序构建成浏览器可以读取的内容,因此您的应用程序可以使用 SFC 或 .vue 文件而不是纯 JavaScript。 这些文件是您创建小块可扩展和可重用代码的一种方式。 如果您要更改一个组件,它将随处更新。
这些 .vue 组件通常由以下三个元素组成:<template>、<script> 和 <style> 元素。 SFC 组件可以具有 scoped 或 unscoped 样式。 当组件具有范围样式时,这意味着 <style> 标签之间的 CSS 只会影响同一文件中 <template> 中的 HTML。 如果组件具有非范围样式,则 CSS 将影响父组件及其子组件。
随着您的项目成功设置,您现在要将这些机场卡片分解为一个名为 AirportCards.vue 的组件。 就目前而言,App.vue 中的 HTML 不是很可重用。 你将把它分解成它自己的组件,这样你就可以将它导入到这个应用程序的任何其他地方,同时保留功能和视觉效果。
在您的终端中,在 components 目录中创建此 .vue 文件:
touch src/components/AirportCards.vue
在文本编辑器中打开 AiportCards.vue 组件。 为了说明如何使用组件重用代码块,请将大部分代码从 App.vue 文件移动到 AirportCards.vue 组件:
sfc-project/src/components/AirportCards.vue
<template>
<div class="wrapper">
<div v-for="airport in airports" :key="airport.abbreviation" class="card">
<p>{{ airport.abbreviation }}</p>
<p>{{ airport.name }}</p>
<p>{{ airport.city }}, {{ airport.state }}</p>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import data from '@/data/us-airports.js'
export default {
name: 'Airports',
setup() {
const airports = ref(data)
return { airports }
}
}
</script>
<style scoped>
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-column-gap: 1rem;
max-width: 960px;
margin: 0 auto;
}
.card {
border: 3px solid;
border-radius: .5rem;
padding: 1rem;
margin-bottom: 1rem;
}
.card p:first-child {
font-weight: bold;
font-size: 2.5rem;
margin: 1rem 0;
}
.card p:last-child {
font-style: italic;
font-size: .8rem;
}
</style>
保存并关闭文件。
接下来,打开您的 App.vue 文件。 现在您可以清理 App.vue 组件并将 AirportCards.vue 导入其中:
sfc-项目/src/App.vue
<template>
<AirportCards />
</template>
<script>
import AirportCards from '@/components/Airports.vue'
export default {
name: 'App',
components: {
AirportCards
}
}
</script>
<style scoped>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
既然 AirportCards 是一个独立的组件,您可以将它放在 <template> HTML 中,就像放置 <p> 标记一样。
当您在浏览器中打开 localhost:8080 时,什么都不会改变。 仍然会显示相同的三张机场卡片,因为您正在 <AirportCards /> 元素中渲染新的 SFC。
接下来,在模板中再次添加相同的组件,以说明组件的可重用性:
/src/App.vue
<template> <AirportCards /> <airport-cards /> </template> ...
您可能会注意到 AirportCards.vue 的这个新实例使用 kebab-case 而不是 PascalCase。 当引用组件时,Vue 并不关心你使用哪一个。 所有大写的单词和字母都将用连字符分隔并小写。 这同样适用于道具,这将在下一节中解释。
注意: 使用的情况看个人喜好,但一致性很重要。 Vue.js 推荐使用 kebab-case,因为它遵循 HTML 标准。
打开浏览器,访问【X27X】【X31X】。 您会发现卡片重复:
这为您的应用增加了模块化,但数据仍然是静态的。 如果要显示相同的三个机场,则卡片行很有用,但更改数据源将需要更改硬编码数据。 在下一步中,您将通过注册 props 并将数据从父组件向下传递到子组件来进一步扩展此组件。
第三步——利用道具传递数据
在上一步中,您创建了一个 AirportCards.vue 组件,它根据 us-airports.js 文件中的数据渲染了许多卡片。 除此之外,您还将相同的组件引用翻了一番,以说明如何通过在 <template> 中添加该组件的另一个实例来轻松复制代码。
但是,将数据保持静态将使将来难以更改数据。 在使用 SFC 时,如果您将组件视为函数,这会有所帮助。 这些函数是可以接受参数(props)并返回一些东西(HTML)的组件。 对于这种情况,您将数据传递到 airports 参数以返回动态 HTML。
在文本编辑器中打开 AirportCards.vue 组件。 您当前正在从 us-airports.js 文件中导入 data。 删除此导入语句,以及 <script> 标签中的 setup 函数:
sfc-project/src/components/AirportCards.vue
...
<script>
export default {
name: 'Airports',
}
</script>
...
保存文件。 此时,浏览器中不会呈现任何内容。
接下来,通过定义道具继续前进。 这个道具可以命名任何东西; 它只是描述传入的数据并将其与名称相关联。
要创建道具,请在组件中添加 props 属性。 this 的值是一系列键/值对。 key 是 prop 的名称,value 是数据的描述。 最好提供尽可能多的描述:
sfc-project/src/components/AirportCards.vue
...
<script>
export default {
name: 'Airports',
props: {
airports: {
type: Array,
required: true
}
}
}
</script>
...
现在,在 AirportCard.vue 中,属性 airports 指的是传入的数据。 保存并退出文件。
接下来,在文本编辑器中打开 App.vue 组件。 像以前一样,您需要 import 来自 us-airports.js 文件的数据和 import 来自 Vue 的 ref 函数,以使其对 HTML 模板具有反应性:
sfc-项目/src/App.vue
<template>
<AirportCards :airports="usAirports" />
<airport-cards />
</template>
<script>
import { ref } from 'vue'
import AirportCards from '@/components/Airports.vue'
import usAirportData from '@/data/us-airports.js'
export default {
name: 'App',
components: {
AirportCards
},
setup() {
const usAirports = ref(usAirportData)
return { usAirports }
}
}
</script>
如果您打开浏览器并访问localhost:8080,您会发现和以前一样的美国机场:
您的模板中有另一个 AirportCards.vue 实例。 由于您在该组件中定义了道具,因此您可以传递具有相同结构的任何数据来呈现来自不同机场的许多卡片。 这是初始设置中的 eu-airports.js 文件的来源。
在 App.vue 中,导入 eu-airports.js 文件,将其包装在 ref 函数中以使其具有反应性,然后返回:
/src/App.vue
<template>
<AirportCards :airports="usAirports" />
<airport-cards :airports="euAirports" />
</template>
<script>
...
import usAirportData from '@/data/us-airports.js'
import euAirportData from '@/data/eu-airports.js'
export default {
...
setup() {
const usAirports = ref(usAirportData)
const euAirports = ref(euAirportData)
return { usAirports, euAirports }
}
}
</script>
...
打开浏览器并访问 localhost:8080。 您会发现在美国机场数据下方呈现的欧洲机场数据:
您现在已经成功地将不同的数据集传递到同一个组件中。 使用 props,您实际上是在将数据重新分配给一个新名称,并使用该新名称来引用子组件中的数据。
在这一点上,这个应用程序开始变得更加动态。 但是,您仍然可以做一些其他事情来使它更加集中和可重用。 在 Vue.js 中,您可以使用称为 slots 的东西。 在下一步中,您将创建一个具有默认 slot 的 Card.vue 组件,该组件将 HTML 注入占位符。
第 4 步 — 使用插槽创建通用卡组件
插槽是创建可重用组件的好方法,尤其是当您不知道该组件中的 HTML 是否相似时。 在上一步中,您创建了另一个具有不同数据的 AirportCards.vue 实例。 在该示例中,每个 HTML 都是相同的。 它是带有段落标签的 v-for 循环中的 <div>。
打开终端并使用 touch 命令创建一个新文件。 此文件将命名为 Card.vue:
touch src/components/Card.vue
在您的文本编辑器中,打开新的 Card.vue 组件。 您将从 AirportCards.vue 中获取一些 CSS 并将其添加到这个新组件中。
创建一个 <style> 标签并添加以下 CSS:
sfc-project/src/components/Card.vue
<style>
.card {
border: 3px solid;
border-radius: .5rem;
padding: 1rem;
margin-bottom: 1rem;
}
</style>
接下来,为这个组件创建 HTML 模板。 在 <style> 标签之前,添加一个 <template> 标签,其内容如下:
sfc-project/src/components/Card.vue
<template> <div class="card"> </div> </template> ...
在 <div class="card"> 之间,添加 <slot /> 组件。 这是 Vue 提供给你的一个组件。 无需导入该组件; 它由 Vue.js 全局导入:
sfc-project/src/components/Card.vue
<template>
<div class="card">
<slot />
</div>
</template>
此 slot 是 HTML 的占位符,在其他地方引用时位于 Card.vue 组件的标签之间。
保存并退出文件。
现在回到 AirportCards.vue 组件。 首先,导入刚刚创建的新的 Card.vue SFC:
sfc-project/src/components/AirportCards.vue
...
<script>
import Card from '@/components/Card.vue'
export default {
name: 'Airports',
props: { ... },
components: {
Card
}
}
</script>
...
现在剩下的就是用 <card> 替换 <div>:
/src/components/AirportCards.vue
<template>
<div class="wrapper">
<card v-for="airport in airports" :key="airport.abbreviation">
<p>{{ airport.abbreviation }}</p>
<p>{{ airport.name }}</p>
<p>{{ airport.city }}, {{ airport.state }}</p>
</card>
</div>
</template>
...
由于您的 Card.vue 组件中有一个 <slot />,因此 <card> 标记之间的 HTML 被注入到它的位置,同时保留了与卡片关联的所有样式。
保存文件。 当您在 localhost:8080 中打开浏览器时,您会发现与以前相同的卡。 现在的不同之处在于您的 AirportCards.vue 现在引用了 Card.vue 组件:
要显示插槽的强大功能,请在应用程序中打开 App.vue 组件并导入 Card.vue 组件:
sfc-项目/src/App.vue
...
<script>
...
import Card from '@/components/Card.vue'
export default {
...
components: {
AirportCards,
Card
},
setup() { ... }
}
</script>
...
在 <template> 中,在 <airport-cards /> 实例下添加以下内容:
sfc-项目/src/App.vue
<template>
<AirportCards :airports="usAirports"/>
<airport-cards :airports="euAirports" />
<card>
<p>US Airports</p>
<p>Total: {{ usAirports.length }}</p>
</card>
<card>
<p>EU Airports</p>
<p>Total: {{ euAirports.length }}</p>
</card>
</template>
...
保存文件并在浏览器中访问localhost:8080。 您的浏览器现在将呈现显示数据集中机场数量的其他元素:
<card /> 标签之间的 HTML 并不完全相同,但它仍然呈现通用卡片。 利用插槽时,您可以使用此功能来创建具有多种不同用途的小型、可重复使用的组件。
结论
在本教程中,您创建了单文件组件并使用 props 和 slots 创建可重用的代码块。 在项目中,您创建了一个 AirportCards.vue 组件,用于渲染许多机场卡片。 然后,您将 AirportCards.vue 组件进一步分解为具有默认插槽的 Card.vue 组件。
你最终得到了许多动态的组件,它们可以用于多种不同的用途,同时保持代码的可维护性并符合 DRY 软件原则。
想要了解更多关于Vue组件的知识,建议通过Vue文档做好准备。 有关 Vue 的更多教程,请查看 如何使用 Vue.js 开发网站系列页面。