第 1 节 概述
上一篇文章中,所有代码都写在同一个文件里。
页面一复杂,这个文件就会变得很长,难以维护。
组件就是解决这个问题的方法。你可以把页面拆成多个独立的小块,每个块只负责自己的事。最后拼在一起,组成完整的页面。
第 2 节 什么是组件
组件可以理解为一个"可复用的自定义标签"。
比如,一个按钮在很多地方都用到了相同的样式和逻辑。可以把它提取成一个组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <!-- SubmitButton.vue -->
<template>
<button
class="px-4 py-2 rounded bg-blue-500 text-white text-sm"
@click="handleClick"
>
提交
</button>
</template>
<script setup lang="ts">
function handleClick() {
console.log('提交按钮被点击了')
}
</script>
|
在其他组件中引用它:
1
2
3
4
5
6
7
8
| <template>
<SubmitButton />
<SubmitButton></SubmitButton> <!-- 两种写法都可以 -->
</template>
<script setup lang="ts">
import SubmitButton from '@/components/SubmitButton.vue'
</script>
|
第 3 节 命名规范
Vue 3 对组件名有两个约定:
| 规则 | 说明 | 示例 |
|---|
| PascalCase | 首字母大写 | SubmitButton、UserCard |
| 至少两个单词 | 避免和 HTML 原生标签冲突 | ✅ SubmitButton ❌ Button |
单项导出的组件和 TypeScript 功能类似,可以这样写:
1
2
3
| <script setup lang="ts">
import SubmitButton from '@/components/SubmitButton.vue'
</script>
|
第 4 节 组件注册
在 Vue 3 中,组件默认是局部注册的。
什么叫局部注册?就是你用 import 导入组件后,只能在当前文件中使用它。不同的页面即使用到同一个组件,也需要各自导入。
这样做的优点是"用了才加载",不会额外增加其他页面的体积。
说明
全局注册也可以做到,但很少用。全局注册会让组件永远被打包,即使有些页面用不到它。
第 5 节 组件拆分的原则
什么时候该拆成一个新组件?看几个信号:
| 信号 | 说明 |
|---|
| 代码太长 | 单个文件超过 200~300 行,考虑拆分 |
| 可复用 | 同一段代码在多个地方出现 |
| 功能独立 | 某块逻辑可以独立描述(如用户卡片、评论列表) |
| 测试需要 | 某个功能需要单独测试 |
一个常见的拆分方法是按"页面 → 区块 → 小组件“的层次来拆分:
1
2
3
4
5
| 页面组件(src/pages/Home.vue)
├─ 头部区域(Header.vue)
├─ 文章列表(ArticleList.vue)
│ └─ 文章卡片(ArticleCard.vue)
└─ 底部区域(Footer.vue)
|
技巧
刚开始练习时,不用追求完美拆分。先写在一起,等发现某个部分重复使用了,再把它提取出来。重构是正常的开发过程。
第 6 节 生命周期钩子在实践中的应用
上一篇介绍了生命周期钩子的基本用法。这里用几个实际场景加深理解:
6.1 场景一:进入页面时获取数据
这是最常用的模式。在 onMounted 中调用 API:
1
2
3
4
5
6
7
8
9
10
| <script setup lang="ts">
import { ref, onMounted } from 'vue'
const articles = ref<Article[]>([])
onMounted(async () => {
const response = await fetch('/api/articles')
articles.value = await response.json()
})
</script>
|
6.2 场景二:离开页面时清理
onUnmounted 用来清理定时器、取消订阅:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| <script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const now = ref(new Date().toLocaleString())
let timer: number
onMounted(() => {
timer = setInterval(() => {
now.value = new Date().toLocaleString()
}, 1000)
})
onUnmounted(() => {
clearInterval(timer)
})
</script>
<template>
<p>当前时间:{{ now }}</p>
</template>
|
6.3 场景三:数据变化时做额外操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <script setup lang="ts">
import { ref, watch, onUpdated } from 'vue'
const items = ref<string[]>([])
// 1. 用 watch 监听数据变化
watch(items, (newList) => {
console.log('列表长度变化:', newList.length)
})
// 2. 用 onUpdated 在 DOM 更新后操作
onUpdated(() => {
console.log('DOM 已经更新')
})
</script>
|
组件是 Vue 3 中组织代码的基本单元。
现在你有了多个组件,但每个组件是独立的。
下一步要解决的矛盾是:父组件怎么把数据传给子组件?子组件怎么通知父组件?