组件介绍
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:
这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。
定义一个组件
当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue
文件中,这被叫做单文件组件 (简称 SFC):
<script setup>
import { ref } from "vue";
const count = ref(0);
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
这里的模板是一个内联的 JavaScript 字符串,Vue 将会在运行时编译它。你也可以使用 ID 选择器来指向一个元素 (通常是原生的 <template>
元素),Vue 将会使用其内容作为模板来源。
上面的例子中定义了一个组件,并在一个 .js
文件里默认导出了它自己,但你也可以通过具名导出在一个文件中导出多个组件。
使用组件
我们会在接下来的指引中使用 SFC 语法,无论你是否使用构建步骤,组件相关的概念都是相同的。示例一节中展示了两种场景中的组件使用情况。
要使用一个子组件,我们需要在父组件中导入它。假设我们把计数器组件放在了一个叫做 ButtonCounter.vue
的文件中,这个组件将会以默认导出的形式被暴露给外部。
<script setup>
import ButtonCounter from "./ButtonCounter.vue";
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
通过 <script setup>
,导入的组件都在模板中直接可用。
当然,你也可以全局地注册一个组件,使得它在当前应用中的任何组件上都可以使用,而不需要额外再导入。关于组件的全局注册和局部注册两种方式的利弊,我们放在了组件注册这一章节中专门讨论。
组件可以被重用任意多次:
<h1>Here is a child component!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />
你会注意到,每当点击这些按钮时,每一个组件都维护着自己的状态,是不同的 count
。这是因为每当你使用一个组件,就创建了一个新的* *实例**。
在单文件组件中,推荐为子组件使用 PascalCase
的标签名,以此来和原生的 HTML 元素作区分。虽然原生 HTML 标签名是不区分大小写的,但 Vue 单文件组件是可以在编译中区分大小写的。我们也可以使用 />
来关闭一个标签。
如果你是直接在 DOM 中书写模板 (例如原生 <template>
元素的内容),模板的编译需要遵从浏览器中 HTML 的解析行为。在这种情况下,你应该需要使用 kebab-case
形式并显式地关闭这些组件的标签。
template
<!-- 如果是在 DOM 中书写该模板 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
案例
不使用组件
制作一个折叠面板
需求:现在想要多个收起展开的部分
方案 1: 复制代码
- 代码重复冗余
- 不利于维护
模板标签 - 在这个基础上,把要复用的多复制几份 (讲解不好的地方引出解决方案)
<script setup>
import { ref } from "vue";
const is_show = ref(false);
</script>
<template>
<div class="container">
<h3>案例:折叠面板</h3>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="is_show = !is_show">
{{ isShow ? "收起" : "展开" }}
</span>
</div>
<div class="container" v-show="is_show">
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</div>
</div>
</div>
</template>
<style>
body {
background-color: #ccc;
}
.container {
width: 400px;
margin: 20px auto;
background-color: #fff;
border: 4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
padding: 1em 2em 2em;
}
h3 {
text-align: center;
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 1em;
}
.title h4 {
line-height: 2;
margin: 0;
}
.container {
border: 1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
上面代码复制 3 份,发生变化一起变化
解决方案:不同的部分,用不同的 isShow 变量
<script setup>
import { ref } from "vue";
const is_show = ref(false);
const is_show1 = ref(false);
const is_show2 = ref(false);
</script>
<template>
<div class="container">
<h3>案例:折叠面板</h3>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="is_show = !is_show">
{{ is_show ? "收起" : "展开" }}
</span>
</div>
<div class="container" v-show="is_show">
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</div>
</div>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="is_show1 = !is_show1">
{{ is_show1 ? "收起" : "展开" }}
</span>
</div>
<div class="container" v-show="is_show1">
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</div>
</div>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="is_show2 = !is_show2">
{{ is_show2 ? "收起" : "展开" }}
</span>
</div>
<div class="container" v-show="is_show2">
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</div>
</div>
</div>
</template>
总结:代码非常的冗余和重复吧?解决方案呢?就是采用我们的组件化开发的方式,往下看
使用组件
组件是可复用的 Vue 实例,封装标签,样式和 JS 代码
组件化 :封装的思想,把页面上 可重用的部分
封装为 组件
,从而方便项目的 开发 和 维护
一个页面,可以拆分成一个个组件,一个组件就是一个整体,每个组件可以有自己独立的 结构 样式 和 行为 (html, css 和 js)
@ 基础使用
目标:每个组件都是一个独立的个体,代码里体现为一个独立的.vue 文件
口诀:哪部分标签复用,就把哪部分封装到组件内
步骤:
创建组件
components/Panel.vue
封装 html + css + js - 组件都是独立的,为了复用
<script setup>
import { ref } from "vue";
const is_show = ref(false);
</script>
<template>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="is_show = !is_show">
{{ is_show ? "收起" : "展开" }}
</span>
</div>
<div class="container" v-show="is_show">
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</div>
</div>
</template>
<style scoped>
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 1em;
}
.title h4 {
line-height: 2;
margin: 0;
}
.container {
border: 1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
scoped 作用
目的:解决多个组件样式名相同,冲突问题
需求:div 标签名选择器,设置背景色
问题:发现组件里的 div 和外面的 div 都生效了
解决:给 Panel.vue 组件里 style 标签上加 scoped 属性即可
<style scoped></style>
在 style 上加入 scoped 属性,就会在此组件的标签上加上一个随机生成的 data-v 开头的属性
而且必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到
总结:style 上加 scoped, 组件内的样式只在当前 vue 组件生效