Vue.js: 组件基础
最后更新:2026-10-20
Vue 组件是可复用的 Vue 实例——每个组件封装自己的模板、逻辑、样式。Vue 3 的核心架构就是"组件树":根组件(App.vue)→ 页面组件 → 业务组件 → 基础组件(Button/Input)。
掌握组件基础是写大型 Vue 应用的关键一步。本课帮你理解组件的本质、注册方式、命名规范,以及为什么 SFC(单文件组件)是 Vue 推荐的写法。
1. 你将学到
- 组件是什么,组件与 Vue 实例的关系
- 单文件组件 SFC 的 3 部分(template / script / style)
- 全局注册 vs 局部注册的区别和选择
- 组件命名规范(PascalCase / kebab-case)
- 组件复用(避免重复代码)
- props 和 emit 的基础概念(详细下两课)
- 5 个组件设计原则
2. 一个 30 页电商后台的代码重复灾难
(1) 痛点:30 个商品卡片,复制粘贴 30 次
Alice 的电商后台需要在多个页面显示 30 个商品卡片。她最初的方案:
HTML
<!-- ❌ 翻车版:30 个商品卡片复制 30 次 -->
<!-- HomeView.vue 中 -->
<div class="product-card">
<img src="iphone.jpg">
<h3>iPhone 15 Pro</h3>
<p>$999</p>
<button>Add to Cart</button>
</div>
<div class="product-card">
<img src="macbook.jpg">
<h3>MacBook Pro</h3>
<p>$2499</p>
<button>Add to Cart</button>
</div>
<!-- ... 还有 28 个 ... -->
<!-- 同样的代码又出现在 ProductList.vue、SearchResults.vue、Cart.vue 中 -->
产品经理 Charlie 追加了 5 个新需求:
"Alice,我要把按钮文字从'Add to Cart'改成'Add to Bag'。另外卡片要显示库存状态。还需要做移动端响应式。"
Alice 不得不修改 30+ 个 HTML 文件 × 3 次 = 90 次编辑。这根本无法维护。
(2) Vue 组件解法:1 个 ProductCard.vue,30 处复用
1 个 ProductCard.vue → 30 处复用。改按钮文字只需改 1 处。
(3) 收益
组件化后:
- 代码量:30 × 15 行 × 3 页 = 1350 行 → 1 个组件 + 30 行 × 3 = 90 行(-93%)
- 改 1 处:30 个产品卡片自动同步更新
- 加新功能:加到组件里,所有页面自动有
- 可测试性:ProductCard.vue 可单独测试
3. 组件是什么?
(1) 定义
组件(Component)是 Vue 中可复用的、有独立逻辑和样式的 UI 单元。每个组件 = 1 个 Vue 实例 + 自己的 data/methods/lifecycle。
graph TB
A[App.vue<br/>根组件] --> B[HomeView.vue<br/>首页]
A --> C[AboutView.vue<br/>关于]
A --> D[DashboardView.vue<br/>仪表盘]
B --> E[ProductCard.vue<br/>商品卡片]
B --> F[ProductList.vue<br/>商品列表]
B --> G[FilterBar.vue<br/>筛选栏]
E --> H[Button.vue<br/>按钮]
E --> I[Badge.vue<br/>徽章]
F --> E
G --> J[Select.vue<br/>下拉]
G --> K[Input.vue<br/>输入]
style A fill:#42b883,color:#fff
style E fill:#42b883,color:#fff
style H fill:#42b883,color:#fff
(2) 5 大核心特点
| 特点 | 说明 |
|---|---|
| 可复用 | 1 个组件可在 N 处使用 |
| 封装性 | 自己的 data / methods / style,互不干扰 |
| 可组合 | 小组件组合成大组件 |
| 可测试 | 单元测试单个组件 |
| 可维护 | 改 1 处 = 改 N 处 |
(3) 组件 vs 函数
| 维度 | 函数 | 组件 |
|---|---|---|
| 复用单位 | 代码逻辑 | UI + 逻辑 |
| 输入 | 参数 (args) | props |
| 输出 | return | emit |
| 状态 | 局部变量 | data / refs |
| 副作用 | 无 | 生命周期钩子 |
▶ 示例:在 3 个页面复用 ProductCard
▶ 试一试输出:
TEXT
首页 Featured:iPhone $999 | 库存: 50、MacBook $2499 | 库存: 20
商品列表 All:上面两张 + iPad $599 | 库存: 30
分类 Phones:只有 iPhone $999 | 库存: 50
同一个 ProductCard 组件渲染了 6 次,页面无重复代码
4. 单文件组件 SFC
(1) 什么是 SFC?
SFC(Single File Component)= 1 个 .vue 文件 = 1 个组件。把 template、script、style 集中在一个文件里,便于管理。
(2) SFC 3 部分详解
| 部分 | 作用 | 必填? | 语言 |
|---|---|---|---|
<template> |
HTML 模板 | ✅ | HTML + Vue 指令 |
<script setup> |
JS 逻辑 | ✅ | JavaScript / TypeScript |
<style scoped> |
CSS 样式 | ❌ | CSS / SCSS / Less |
(3) SFC vs HTML 字符串
JS
// ❌ 不用 SFC(字符串模板,Vue 2 风格)
Vue.component('my-component', {
template: '<div>{{ msg }}</div>',
data() { return { msg: 'Hello' } }
})
// ✅ 用 SFC(推荐)
// MyComponent.vue
<template>
<div>{{ msg }}</div>
</template>
<script setup>
const msg = 'Hello'
</script>
SFC 优势:
- 编辑器支持(高亮、跳转、自动补全)
- 工具链支持(Vite 热更新、TypeScript 类型检查)
- 关注点分离(HTML/JS/CSS 各 1 块)
5. 组件注册
(1) 全局注册(不推荐大型项目)
JS
// main.js
const { createApp } = Vue
import App from './App.vue'
import ProductCard from './components/ProductCard.vue'
const app = createApp(App)
// 全局注册:所有组件都能用 <ProductCard />
app.component('ProductCard', ProductCard)
app.mount('#app')
(2) 局部注册(推荐)
VUE
<!-- HomeView.vue -->
<script setup>
// 1. 导入
import ProductCard from '@/components/ProductCard.vue'
// 2. 在模板中使用(无需注册,自动可用)
</script>
<template>
<ProductCard :product="product" />
</template>
(3) 两种注册对比
| 维度 | 全局注册 | 局部注册 |
|---|---|---|
| 可用范围 | 整个应用 | 当前组件 |
| 代码位置 | main.js |
组件内 <script setup> |
| Tree-shaking | ❌ 不支持 | ✅ 支持 |
| 类型提示 | 弱 | 强(IDE 自动补全) |
| 推荐度 | ⭐⭐ 插件用 | ⭐⭐⭐⭐⭐ 业务用 |
▶ 示例:全局注册 vs 局部注册对比
▶ 试一试输出:
TEXT
页面上只剩 1 个蓝色按钮"局部注册按钮"(#3b82f6)
#app 里静态写的 h3 和按钮 A/B/C 被 App 的 template 整体替换掉
MyButton 只在 App.components 里局部注册,其他地方直接用会提示组件解析失败
6. 组件命名规范
(1) 3 种命名风格
| 风格 | 例子 | 用法 | 推荐度 |
|---|---|---|---|
| PascalCase | ProductCard.vue |
JS import / 模板中 | ⭐⭐⭐⭐⭐ |
| kebab-case | product-card.vue |
文件名 / kebab-case 模板 | ⭐⭐⭐ |
| camelCase | productCard.vue |
不推荐 | ⭐ |
(2) 模板中使用
(3) 文件夹结构
TEXT
src/
├── components/ # 公共组件(跨页面用)
│ ├── ProductCard.vue
│ ├── Button.vue
│ └── Header.vue
├── views/ # 页面组件(路由用)
│ ├── HomeView.vue
│ ├── ProductListView.vue
│ └── DashboardView.vue
├── composables/ # 组合式函数
│ ├── useAuth.ts
│ └── useFetch.ts
├── router/ # 路由配置
└── App.vue # 根组件
7. props 和 emit 基础
(1) props:父传子
(2) emit:子传父
(3) props vs emit 速查
| 方向 | API | 用途 |
|---|---|---|
| 父 → 子 | defineProps() |
数据传递 |
| 子 → 父 | defineEmits() |
事件通知 |
▶ 示例:props 5 大类型 + 验证器
▶ 试一试输出:
TEXT
管理员 - Alice
年龄: 25 | 状态: 在线
角色: admin | 框架: Vue, React, Angular
邮箱: alice@example.com | 每页: 20
(1) 📊 5 个组件设计原则
| 原则 | 说明 |
|---|---|
| 单一职责 | 1 个组件只做 1 件事 |
| 可复用 | 抽象通用 UI(Button/Input/Modal) |
| 可配置 | props 控制行为,slot 控制内容 |
| 可组合 | 小组件组合成大组件 |
| 易测试 | 组件独立,便于单元测试 |
(2) 📊 5 个常见错误
| 错误 | 现象 | 解决 |
|---|---|---|
| 组件名单文件 | IDE 报错 | 用 PascalCase:ProductCard.vue |
| 全局注册太多 | bundle 变大 | 局部注册支持 tree-shaking |
| 组件太大 | 难维护 | 拆成小组件 |
| props 直接修改 | Vue 警告 | props 只读,要改用 emit |
| 忘记 import 组件 | 模板显示 "unknown component" | 检查 <script setup> 中的 import |
8. 综合演练:ProductCard 组件全貌
▶ 示例:ProductCard 组件(CDN 多组件演示)
▶ 展开运行示例(65 行)
输出:
TEXT
Featured Products
iPhone 15 Pro / $999 / [Add to Cart]
MacBook Pro / $2499 / Only 5 left / [Add to Cart]
Sold Out Item / $99 / [Out of Stock](整卡半透明,按钮禁用不可点)
购物车商品数: 0 → 点前两张卡的按钮变成 1、2…
❓ 常见问题
Q 什么时候用全局注册?
A 只在 2 种情况:(1) 插件式组件(如 Element Plus 的
<el-button>);(2) 整个应用都要用的基础组件(Button/Icon)。业务组件一律局部注册,tree-shaking 才能生效。Q SFC 必须 3 部分都有吗?
A
<template> 和 <script> 必填,<style> 可选。如果组件只有 JS(比如只是封装数据),可以没有 template(用 render 函数)。Q 组件文件多大合适?
A 建议 < 200 行。超过 300 行考虑拆分。组件越简单越好维护。
Q SFC 用 .vue 后缀,其他工具怎么识别?
A Vite / Webpack / Vue CLI 都默认识别 .vue 后缀。VS Code 装 "Vue - Official" 扩展。
Q 如何测试单个组件?
A 用 Vitest + Vue Test Utils(@vue/test-utils):
mount(ProductCard, { props: { product: mockProduct } })。本教程 Phase 4.6 详细介绍。Q 组件库(Element Plus)怎么用?
A npm 安装 → main.js 全局注册 → 模板中直接用
<el-button>。具体看 Phase 4.7 章节。Q SFC 和 React JSX 的区别?
A SFC 用 HTML-like 模板(接近 web 标准),React 用 JSX(JS 写 HTML)。SFC 学习曲线平缓,JSX 灵活但需要 build。Vue 3 也支持 JSX(@vitejs/plugin-vue-jsx),但 SFC 是默认推荐。
📖 小节
- 组件是可复用的 Vue 实例,封装 template / script / style 三部分
- SFC(.vue 文件)是 Vue 3 推荐写法,编辑器 + 工具链支持好
- 局部注册 > 全局注册(tree-shaking 友好)
- 命名规范:PascalCase(ProductCard)> kebab-case(product-card)
- props 父传子(defineProps),emit 子传父(defineEmits)
- 组件设计原则:单一职责、可复用、可配置、可组合、易测试
- 文件夹结构:components/ 公共组件、views/ 页面组件、composables/ 组合函数
📝 作业
-
基础题(难度⭐) 写一个 Button 组件
src/components/BaseButton.vue:- props:
text(String),variant(String, 'primary'/'success'/'danger') - 3 种 variant 对应 3 种 class
- emit
click事件 - 在 HomeView.vue 中使用 3 个不同 variant
- props:
-
进阶题(难度⭐⭐) 写一个商品列表 + 卡片系统:
- ProductCard.vue:单个商品卡片(props: product, emit: add-to-cart)
- ProductList.vue:商品列表容器(v-for + 过滤)
- App.vue:根组件,传数据 + 处理 add-to-cart 事件
- 3 个文件 3 个组件嵌套使用
-
挑战题(难度⭐⭐⭐) 实现一个完整的"用户卡片"组件系统:
- Avatar.vue:头像组件(支持 url / initials 两种模式)
- UserCard.vue:用户卡片(用 Avatar)
- UserList.vue:用户列表(用 UserCard)
- App.vue:根组件(数据 + 交互)
- 5 个 props(avatar/name/email/role/active)
- 3 个 emit 事件(edit/delete/select)
- scoped 样式隔离