Skip to content
第 5 章 前端 ⏱ 12 分钟阅读

第 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 冲突

动手练习 ​

  1. 用户卡片:写 UserCard.vue,接收 name / avatar / bio 三个 prop
  2. 按钮组件:写 MyButton.vue,支持 type(primary/danger) / disabled
  3. 卡片容器:写 Card.vue,用 <slot> 让父组件塞任意内容

下一章:第 6 章:组件通信 →

本站基于 VitePress 构建 · 由 StackHub 团队维护