组件基础 #
一、组件概述 #
组件是Vue最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。
1.1 组件的优点 #
- 复用性:一次编写,多处使用
- 维护性:逻辑独立,便于维护
- 可组合:小组件组合成大组件
- 封装性:隐藏内部实现细节
1.2 组件树结构 #
text
App (根组件)
├── Header
│ ├── Logo
│ └── Navigation
├── Main
│ ├── Sidebar
│ └── Content
│ ├── Article
│ └── Comments
└── Footer
二、定义组件 #
2.1 单文件组件(SFC) #
vue
<!-- MyComponent.vue -->
<template>
<div class="my-component">
<h2>{{ title }}</h2>
<p>{{ content }}</p>
</div>
</template>
<script>
export default {
data() {
return {
title: '组件标题',
content: '组件内容'
}
}
}
</script>
<style scoped>
.my-component {
padding: 20px;
border: 1px solid #ddd;
}
</style>
2.2 使用setup语法糖 #
vue
<!-- MyComponent.vue -->
<template>
<div>
<h2>{{ title }}</h2>
<button @click="handleClick">点击</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const title = ref('组件标题')
function handleClick() {
console.log('点击了')
}
</script>
2.3 defineComponent函数 #
vue
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'MyComponent',
setup() {
const count = ref(0)
function increment() {
count.value++
}
return {
count,
increment
}
},
template: `
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+1</button>
</div>
`
})
</script>
2.4 字符串模板组件 #
javascript
import { createApp } from 'vue'
const app = createApp({})
// 使用字符串模板定义组件
app.component('MyButton', {
template: '<button class="btn"><slot/></button>'
})
三、注册组件 #
3.1 全局注册 #
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import MyComponent from './components/MyComponent.vue'
const app = createApp(App)
// 全局注册
app.component('MyComponent', MyComponent)
app.component('MyButton', {
template: '<button><slot/></button>'
})
app.mount('#app')
vue
<!-- 任何组件都可以直接使用 -->
<template>
<MyComponent />
<MyButton>按钮</MyButton>
</template>
3.2 局部注册 #
vue
<template>
<MyComponent />
<MyButton>按钮</MyButton>
</template>
<script>
import MyComponent from './MyComponent.vue'
import MyButton from './MyButton.vue'
export default {
components: {
MyComponent,
MyButton
}
}
</script>
3.3 setup语法糖中的局部注册 #
vue
<template>
<MyComponent />
<MyButton>按钮</MyButton>
</template>
<script setup>
import MyComponent from './MyComponent.vue'
import MyButton from './MyButton.vue'
// 导入的组件直接可用,无需显式注册
</script>
3.4 全局vs局部注册 #
| 特性 | 全局注册 | 局部注册 |
|---|---|---|
| 使用范围 | 所有组件 | 当前组件 |
| Tree-shaking | 不支持 | 支持 |
| 构建体积 | 较大 | 较小 |
| 适用场景 | 基础组件 | 业务组件 |
四、组件命名 #
4.1 命名规范 #
javascript
// 推荐:多词名称,避免与HTML元素冲突
app.component('TodoItem', { /* ... */ })
app.component('TodoList', { /* ... */ })
// 不推荐:单字名称
// app.component('Item', { /* ... */ })
4.2 在模板中使用 #
vue
<template>
<!-- 推荐使用kebab-case -->
<todo-item />
<todo-list />
<!-- 也可以使用PascalCase -->
<TodoItem />
<TodoList />
</template>
4.3 name选项 #
vue
<script>
export default {
name: 'TodoItem',
// ...
}
</script>
<script setup>
// Vue 3.3+ 使用defineOptions
defineOptions({
name: 'TodoItem'
})
</script>
五、组件Props #
5.1 基本使用 #
vue
<!-- 子组件 Child.vue -->
<template>
<div>
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div>
</template>
<script setup>
defineProps({
title: String,
content: String
})
</script>
vue
<!-- 父组件 -->
<template>
<Child title="标题" content="内容" />
</template>
5.2 Props定义方式 #
vue
<script setup>
// 简单声明
defineProps(['title', 'content'])
// 对象声明(带类型)
defineProps({
title: String,
count: Number,
isActive: Boolean
})
// 完整声明(带验证)
defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
},
items: {
type: Array,
default: () => []
},
user: {
type: Object,
default: () => ({})
},
validator: {
validator(value) {
return ['success', 'warning', 'danger'].includes(value)
}
}
})
</script>
5.3 Props类型 #
vue
<script setup>
defineProps({
// 基本类型
stringProp: String,
numberProp: Number,
booleanProp: Boolean,
arrayProp: Array,
objectProp: Object,
functionProp: Function,
symbolProp: Symbol,
// 多种类型
multipleType: [String, Number],
// 自定义类
dateProp: Date,
// 自定义构造函数
customProp: CustomClass
})
</script>
5.4 传递Props #
vue
<template>
<!-- 静态传递 -->
<Child title="静态标题" />
<!-- 动态传递 -->
<Child :title="dynamicTitle" />
<!-- 传递数字 -->
<Child :count="100" />
<!-- 传递布尔值 -->
<Child :is-active="true" />
<!-- 传递数组 -->
<Child :items="['a', 'b', 'c']" />
<!-- 传递对象 -->
<Child :user="{ name: '张三', age: 25 }" />
<!-- 传递对象的所有属性 -->
<Child v-bind="userObject" />
</template>
5.5 单向数据流 #
vue
<script setup>
const props = defineProps({
count: {
type: Number,
default: 0
}
})
// ❌ 不要直接修改props
// props.count++
// ✅ 使用本地数据
import { ref, computed } from 'vue'
const localCount = ref(props.count)
// ✅ 使用计算属性
const doubledCount = computed(() => props.count * 2)
</script>
六、组件事件 #
6.1 定义和触发事件 #
vue
<!-- 子组件 Child.vue -->
<template>
<button @click="handleClick">点击</button>
</template>
<script setup>
const emit = defineEmits(['update', 'delete'])
function handleClick() {
emit('update', { id: 1, name: 'updated' })
}
</script>
vue
<!-- 父组件 -->
<template>
<Child
@update="handleUpdate"
@delete="handleDelete"
/>
</template>
<script setup>
function handleUpdate(data) {
console.log('更新:', data)
}
function handleDelete(id) {
console.log('删除:', id)
}
</script>
6.2 事件验证 #
vue
<script setup>
const emit = defineEmits({
submit: ({ email, password }) => {
if (!email || !password) {
console.warn('邮箱和密码不能为空')
return false
}
return true
},
// 无验证
click: null
})
function handleSubmit() {
emit('submit', { email: 'test@example.com', password: '123456' })
}
</script>
6.3 v-model实现 #
vue
<!-- 子组件 CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
vue
<!-- 父组件 -->
<template>
<CustomInput v-model="text" />
</template>
6.4 多个v-model #
vue
<!-- 子组件 UserForm.vue -->
<template>
<input :value="firstName" @input="$emit('update:firstName', $event.target.value)">
<input :value="lastName" @input="$emit('update:lastName', $event.target.value)">
</template>
<script setup>
defineProps(['firstName', 'lastName'])
defineEmits(['update:firstName', 'update:lastName'])
</script>
vue
<!-- 父组件 -->
<template>
<UserForm
v-model:first-name="first"
v-model:last-name="last"
/>
</template>
七、插槽 #
7.1 基本插槽 #
vue
<!-- 子组件 Card.vue -->
<template>
<div class="card">
<slot>默认内容</slot>
</div>
</template>
vue
<!-- 父组件 -->
<template>
<Card>自定义内容</Card>
<Card /> <!-- 显示默认内容 -->
</template>
7.2 具名插槽 #
vue
<!-- 子组件 Layout.vue -->
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
vue
<!-- 父组件 -->
<template>
<Layout>
<template #header>
<h1>页面标题</h1>
</template>
<template #default>
<p>主要内容</p>
</template>
<template #footer>
<p>页脚信息</p>
</template>
</Layout>
</template>
7.3 作用域插槽 #
vue
<!-- 子组件 List.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="index">
{{ item.name }}
</slot>
</li>
</ul>
</template>
<script setup>
defineProps({
items: Array
})
</script>
vue
<!-- 父组件 -->
<template>
<List :items="users">
<template #default="{ item, index }">
<span>{{ index + 1 }}. {{ item.name }} - {{ item.email }}</span>
</template>
</List>
</template>
7.4 动态插槽名 #
vue
<template>
<Layout>
<template #[dynamicSlotName]>
动态插槽内容
</template>
</Layout>
</template>
<script setup>
import { ref } from 'vue'
const dynamicSlotName = ref('header')
</script>
八、组件实例 #
8.1 访问组件实例 #
vue
<template>
<Child ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
function callChildMethod() {
// 访问子组件暴露的方法
childRef.value.someMethod()
}
</script>
vue
<!-- Child.vue -->
<template>
<div>子组件</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
function someMethod() {
console.log('子组件方法被调用')
}
// 暴露给父组件
defineExpose({
count,
increment,
someMethod
})
</script>
8.2 模板引用 #
vue
<template>
<!-- 访问DOM元素 -->
<input ref="inputRef" />
<button @click="focusInput">聚焦</button>
<!-- v-for中的ref -->
<div v-for="item in items" :ref="setItemRef" :key="item.id">
{{ item.name }}
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
const itemRefs = ref([])
function setItemRef(el) {
if (el) {
itemRefs.value.push(el)
}
}
function focusInput() {
inputRef.value.focus()
}
onMounted(() => {
inputRef.value.focus()
})
</script>
九、动态组件 #
9.1 基本使用 #
vue
<template>
<button @click="current = 'Home'">首页</button>
<button @click="current = 'About'">关于</button>
<component :is="currentComponent" />
</template>
<script setup>
import { ref, computed } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
const current = ref('Home')
const currentComponent = computed(() => {
return current.value === 'Home' ? Home : About
})
</script>
9.2 keep-alive缓存 #
vue
<template>
<button @click="current = 'Home'">首页</button>
<button @click="current = 'About'">关于</button>
<!-- 使用keep-alive缓存组件状态 -->
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</template>
9.3 keep-alive配置 #
vue
<template>
<!-- include - 包含的组件 -->
<keep-alive include="Home,About">
<component :is="currentComponent" />
</keep-alive>
<!-- exclude - 排除的组件 -->
<keep-alive exclude="Settings">
<component :is="currentComponent" />
</keep-alive>
<!-- max - 最大缓存数 -->
<keep-alive :max="10">
<component :is="currentComponent" />
</keep-alive>
</template>
十、异步组件 #
10.1 基本使用 #
vue
<template>
<AsyncComponent />
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
)
</script>
10.2 加载状态 #
vue
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
</script>
10.3 配合Suspense #
vue
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
十一、总结 #
| 概念 | 说明 |
|---|---|
| 单文件组件 | .vue文件定义组件 |
| 全局注册 | app.component() |
| 局部注册 | components选项 |
| Props | 父传子数据 |
| Emits | 子传父事件 |
| 插槽 | 内容分发 |
| 动态组件 | component :is |
| 异步组件 | defineAsyncComponent |
组件基础要点:
- 优先使用单文件组件
- 优先使用局部注册
- Props是单向数据流
- 使用事件进行子传父通信
- 插槽实现内容分发
- 动态组件实现组件切换
最后更新:2026-03-26