作者选择 "开放性精神疾病"作为 "为国家而写"计划的一部分来接受捐赠。
简介
当使用Vue.js创建一个网络应用时,用小的、模块化的代码块来构建你的应用是一个最佳实践。这不仅可以使你的应用程序的各个部分保持集中,而且还可以使应用程序在复杂性增加时更容易更新。由于从Vue CLI生成的应用程序需要一个构建步骤,你可以使用_单文件组件_(SFC)来为你的应用程序引入模块化。SFC的扩展名是.vue ,包含一个HTML<template>,<script>, 和<style> 标签,可以在其他组件中实现。
SFCs给开发者提供了一种方法,可以为他们的每个组件创建自己的HTML标签,然后在他们的应用程序中使用它们。就像<p> HTML标签会在浏览器中渲染一个段落,并且也持有非渲染的功能一样,组件标签会在Vue模板中的任何地方渲染SFC。
在本教程中,你将创建一个SFC,并使用props 来向下传递数据,使用slots 来在标签之间注入内容。在本教程结束时,你将对什么是SFC以及如何处理代码的可重用性有一个大致了解。
前提条件
- 在你的电脑上安装Node.js版本
14.16.0或更高。要在macOS或Ubuntu 20.04上安装,请按照《如何在macOS上安装Node.js并创建本地开发环境》中的步骤,或按照《如何在Ubuntu 20.04上安装Node.js》中的《使用PPA进行安装》部分进行安装 - 在你的机器上安装Vue CLI并生成一个新项目。由于本教程使用了Vue 3 Composition API,请确保在生成应用程序时选择
3.x (Preview)选项。这个项目的名称将是sfc-project,它将作为根目录。 - 你还需要有JavaScript、HTML和CSS的基本知识,你可以在我们的《如何用HTML构建网站》系列、《如何用CSS构建网站》系列和《如何用JavaScript编码》中找到。
第1步 - 设置项目
在本教程中,你将创建一个机场卡片组件,在一系列的卡片中显示一些机场及其代码。在遵循先决条件部分之后,你将有一个新的Vue项目,名为sfc-project 。在本节中,你将把数据导入这个生成的应用程序。这些数据将是一个由一些属性组成的对象数组,你将用它们来在浏览器中显示信息。
一旦项目生成,打开你的终端,cd ,或改变目录进入根src 。
cd sfc-project/src
在那里,用mkdir 命令创建一个名为data 的新目录,然后用touch 命令创建一个名为us-airports.js 的新文件。
mkdir data
touch data/us-airports.js
在你选择的文本编辑器中,打开这个新的JavaScript文件,加入以下本地数据。
sfc-project/data/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'
}
]
这个数据是一个由美国的几个机场组成的对象 数组。这将在本教程后面的单文件组件中呈现。
保存并退出该文件。
接下来,你将创建另一组机场数据。这个数据将由欧洲机场组成。使用touch 命令,创建一个名为eu-airports.js 的新JavaScript文件。
touch data/eu-airports.js
然后打开该文件并添加以下数据。
sfc-project/data/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 文件。在这个文件中,删除img 和HelloWorld 标签中的<template> 和components 部分和import 语句中的<script> 。你的App.vue 将类似于以下内容。
sfc-project/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-project/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-project/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组件可以有_范围内_或_无范围内_的样式。当一个组件有范围内的样式时,这意味着<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-project/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标准。
打开浏览器,访问localhost:8080 。你会发现卡片是重复的。

这给你的应用程序增加了模块化,但数据仍然是静态的。如果你想显示同样的三个机场,但改变数据源就需要改变硬编码的数据,那么这一排卡片就很有用。在下一步中,你将通过注册道具和将数据从父组件向下传递到子组件来进一步扩展这个组件。
第3步--利用道具来传递数据
在上一步中,你创建了一个AirportCards.vue 组件,从us-airports.js 文件中的数据渲染了一些卡片。除此之外,你还将同一个组件的引用增加了一倍,以说明你如何通过在<template> 中添加该组件的另一个实例来轻松地重复代码。
然而,让数据静止不动会让你在未来很难改变数据。在使用SFC时,如果你把组件看作是函数,会有帮助。这些函数是可以接收参数(props)并返回一些东西(HTML)的组件。在这个案例中,你将把数据传入ports参数,以返回动态HTML。
在你的文本编辑器中打开AirportCards.vue 组件。你目前正在从us-airports.js 文件中导入data 。删除这个导入语句,以及<script> 标签中的setup 函数。
sfc-project/src/components/AirportCards.vue
...
<script>
export default {
name: 'Airports',
}
</script>
...
保存该文件。在这一点上,任何东西都不会在浏览器中呈现。
接下来,通过定义一个道具向前推进。这个道具可以被命名为任何东西;它只是描述进来的数据,并将其与一个名字联系起来。
要创建一个道具,在组件中添加props 属性。它的值是一系列的键/值对。键是道具的名称,而值是数据的描述。最好能提供尽可能多的描述。
sfc-project/src/components/AirportCards.vue
...
<script>
export default {
name: 'Airports',
props: {
airports: {
type: Array,
required: true
}
}
}
</script>
...
现在,在AirportCard.vue ,属性airports 是指传入的数据。保存并退出该文件。
接下来,在你的文本编辑器中打开App.vue 组件。ref 像以前一样,你需要从us-airports.js 文件中import 数据,并从Vue中import 函数,使其对HTML模板产生反应。
sfc-project/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中,你可以使用一种叫做_槽_的东西。在下一步中,你将创建一个Card.vue 组件,它有一个默认的slot ,将HTML注入到一个占位符中。
第4步 - 使用槽创建一个普通卡片组件
槽是创建可重复使用的组件的一个好方法,特别是当你不知道该组件中的HTML是否会相似时。在上一个步骤中,你创建了另一个具有不同数据的AirportCards.vue 实例。在那个例子中,每个人的HTML都是一样的。它是一个<div> ,在一个v-for 循环中,带有段落标签。
打开你的终端,使用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 是一个占位符,当其他地方引用Card.vue 组件的标签之间的HTML。
保存并退出该文件。
现在回到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-project/src/App.vue
...
<script>
...
import Card from '@/components/Card.vue'
export default {
...
components: {
AirportCards,
Card
},
setup() { ... }
}
</script>
...
在<template> ,在<airport-cards /> 实例下添加以下内容。
sfc-project/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 组件。
你最终得到了一些组件,这些组件是动态的,可以用于多种不同的用途,同时保持代码的可维护性,符合D.R.Y.软件原则。
要了解更多关于Vue组件的信息,建议准备通过Vue文档。关于Vue的更多教程,请查看《如何用Vue.js开发网站》系列页面。