第 5 章:组件基础
学习目标
- 拆分组件并复用
- 区分全局组件与局部组件
- 理解
defineProps的 TS 写法 - 避开组件注册的 3 个坑
一、最小组件
vue
<!-- src/components/Hello.vue -->
<script setup lang="ts">
defineProps<{ name: string }>()
</script>
<template>
<p>你好,{{ name }}</p>
</template>vue
<!-- 父组件 -->
<script setup lang="ts">
import Hello from '@/components/Hello.vue'
</script>
<template>
<Hello name="Alice" />
</template>二、Props 三种写法
2.1 TS 写法(推荐)
ts
defineProps<{
name: string
age?: number
tags?: string[]
}>()
// 带默认值
withDefaults(
defineProps<{ name?: string; age?: number }>(),
{ name: '匿名', age: 18 },
)2.2 运行时写法(动态 prop 名)
ts
defineProps({
name: { type: String, required: true },
age: { type: Number, default: 18 },
})⚠️ 坑 1:TS 写法不能和运行时写法同时使用,二选一。
三、注册方式
3.1 局部注册(推荐)
vue
<script setup lang="ts">
import Counter from './Counter.vue'
</script><script setup> 默认就是局部,无需 components: {}。
3.2 全局注册
ts
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import Icon from './components/Icon.vue'
const app = createApp(App)
app.component('Icon', Icon) // 所有模板里都能用 <Icon />
app.mount('#app')⚠️ 坑 2:全局组件用得越多,Tree-shaking 越失效,体积会涨。
四、组件命名
ts
// 多词组件名,避免和 HTML 元素冲突
export default { name: 'UserCard' }文件名 UserCard.vue 在 <template> 里既能写 <UserCard> 又能写 <user-card>。
💡 文件名 = 默认组件名,需要
keep-alive/ 递归组件 / DevTools 定位时才用defineOptions({ name: 'xxx' })显式声明——别啥都加。
五、组件复用:插槽雏形
vue
<!-- Card.vue -->
<template>
<div class="card">
<slot>默认内容</slot>
</div>
</template>vue
<Card>
<h2>自定义标题</h2>
</Card>六、组件 data 必须是一个函数
ts
// ⚠️ 错误:data 是对象,所有实例共享
data: { count: 0 }
// ✅ 正确:每个实例独立
data() { return { count: 0 } }<script setup> 用 ref 直接定义,天然独立。
七、实战:评论组件
vue
<!-- CommentItem.vue -->
<script setup lang="ts">
defineProps<{
author: string
content: string
time: string
}>()
</script>
<template>
<div class="comment">
<strong>{{ author }}</strong>
<span class="time">{{ time }}</span>
<p>{{ content }}</p>
</div>
</template>
<style scoped>
.comment { padding: 12px; border-bottom: 1px solid #eee; }
.time { color: #999; font-size: 12px; margin-left: 8px; }
</style>⚠️ 坑 3:
scoped样式不会影响子组件根元素内部,需要:deep()穿透。
八、本章小结
| 概念 | 关键 |
|---|---|
| Props | 父传子,只读 |
defineProps<{}> | TS 写法,推荐 |
| 局部注册 | <script setup> 默认行为 |
| 全局注册 | app.component() |
| 组件名 | 多词大驼峰,避免和 HTML 冲突 |
动手练习
- 用户卡片:写
UserCard.vue,接收name / avatar / bio三个 prop - 按钮组件:写
MyButton.vue,支持type(primary/danger) /disabled - 卡片容器:写
Card.vue,用<slot>让父组件塞任意内容
下一章:第 6 章:组件通信 →