Vue 组件基础:定义、使用与核心机制
组件的核心价值在于将用户界面拆分为独立、可复用的模块。这既实现了代码层面的解耦,也提升了团队协作效率与后期维护的便捷性。
如何定义 Vue 组件
若项目配置了构建工具(如 Vite),通常将 Vue 组件定义在独立的 .vue 文件中,这便是广为人知的单文件组件(SFC),其结构清晰、开发高效。
若未使用构建工具,也可通过纯 JavaScript 对象定义组件,但此方式较少采用,故此处不作深入探讨。
组件的使用与注册
在父组件中引用子组件时,首先需要将其导入,随后即可在父组件的模板中直接作为自定义标签使用。
复制代码<template>
<h1>Here is a child component!h1>
<ButtonCounter />
template>
此外,Vue 还支持全局注册——将组件注册为全局后,任意组件均可直接使用,无需重复导入。
- 另一个关键特性是:每个组件独立运行,维护自身状态。每次使用组件都会创建一个全新的实例,彼此之间互不干扰。
通过 props 传递数据
假设正在开发一个博客系统,希望所有文章拥有统一的视觉风格,但标题和内容各不相同。此时,就需要通过 props 向组件传递动态数据。
props 是一种特殊的属性(attributes)。要使组件能够接收外部数据,需在组件的 props 选项中提前声明。宏 defineProps() 用于告知 Vue 框架该组件预期接收哪些数据:
复制代码<template>
<h4>{{ title }}h4>
template>
声明后的 props 会自动暴露给模板。如需在脚本中进一步操作,defineProps 会返回一个对象,包含所有可传入的 props:
复制代码const props = defineProps(['title'])
console.log(props.title)
若未使用 ,则需通过选项式 API 声明 props,并将其作为 setup() 函数的第一个参数传入:
复制代码export default {
props: ['title'],
setup(props) {
console.log(props.title)
}
}
一个组件可以拥有任意数量的 props,默认情况下所有 prop 均可接受任意类型的值。
复制代码<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />
实际开发中,父组件通常维护一个博客文章数组:
复制代码const posts = ref([
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
])
然后通过 v-for 将数据传入子组件:
复制代码<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
子组件接收并渲染:
复制代码<template>
<h4>{{ title }}h4>
template>
- 注意,这里的
:title="post.title"是v-bind:title="post.title"的缩写。v-bind可将数据动态绑定到 HTML 属性或组件 props 上,实现 HTML 属性与 JavaScript 变量的实时联动。
监听事件:子父组件通信
回到 组件。有时子组件需要与父组件进行交互——例如一个无障碍场景:用户点击按钮后,文章文字放大,但页面其他部分保持默认字号。
在父组件中,可以添加一个 postFontSize 的 ref 来实现此效果:
复制代码const posts = ref([
/* ... */
])const postFontSize = ref(1)
然后在模板中利用它控制所有博客文章的字体大小:
复制代码"{ fontSize: postFontSize + 'em' }">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
接下来,为 组件添加一个按钮:
复制代码
<template>
<div class="blog-post">
<h4>{{ title }}h4>
<button>Enlarge textbutton>
div>
template>
我们期望的效果是:子组件的按钮点击后,能通知父组件放大所有文章的文字。
实现方案依赖 Vue 内置的自定义事件系统。父组件可以通过 v-on 或 @ 选择性监听子组件抛出的自定义事件,这与监听原生 DOM 事件的方式类似:
复制代码<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>
- 这里的
@enlarge-text="postFontSize += 0.1"是v-on:enlarge-text="postFontSize += 0.1"的缩写。含义是:一旦enlarge-text事件被触发,立即执行postFontSize += 0.1。
子组件则通过调用内置的 $emit 方法,传入事件名称来抛出事件:
复制代码
<template>
<div class="blog-post">
<h4>{{ title }}h4>
<button @click="$emit('enlarge-text')">Enlarge textbutton>
div>
template>
由于父组件监听 @enlarge-text="postFontSize += 0.1",一旦收到事件便会更新 postFontSize 的值。
与 props 类似,需要抛出的事件也需通过 defineEmits() 声明:
复制代码
和 defineProps 类似,defineEmits 仅可用于 中,且无需额外导入。
通过插槽分配内容
某些场景下,我们希望像原生 HTML 元素一样向组件中传递内容:
复制代码<AlertBox>
Something bad happened.
AlertBox>
希望渲染出的效果如下:

此需求可通过 Vue 的自定义slot 元素轻松实现:
复制代码
<div class="alert-box">
<strong>This is an Error for Demo Purposesstrong>
<slot />
div>
<style scoped>
.alert-box {
/* ... */
}
style>
也就是说, 就像一个占位符,父组件传入的内容会被自动渲染到该位置。
app.vue
复制代码<template>
<AlertBox>
Something bad happened.
AlertBox>
template>
AlertBox.vue
复制代码
<div class="alert-box">
<strong>Error!strong>
<br/>
<slot />
div>
动态组件
某些场景需要在两个组件之间来回切换,例如 Tab 界面。Vue 提供了 元素和特殊的 is 属性来解决这个问题:
复制代码
<component :is="tabs[currentTab]">component>
传递给 :is 的值可以是以下两种:
- 已注册的组件名
- 导入的组件对象
需要注意的是,当使用 在多个组件间切换时,被切出的组件会被直接卸载。若希望保持它们“存活”状态,可以使用 组件包裹。
App.vue
复制代码
<div class="demo">
<button
v-for="(_, tab) in tabs"
:key="tab"
:class="['tab-button', { active: currentTab === tab }]"
@click="currentTab = tab"
>
{{ tab }}
button>
<component :is="tabs[currentTab]" class="tab">component>
div>
