组件是可复用的Vue实例,带有一个名字。它们允许我们将UI划分为独立的、可复用的部分,并对每个部分进行单独的思考。
应用被分解为一个个独立的组件,每个组件管理自己的状态和视图
一次编写,多处使用
独立开发、测试和维护
可以组合成复杂应用
跨项目复用组件
在Vue中有多种方式创建组件,最常见的是使用Vue.component全局注册:
// 全局注册组件
Vue.component('button-counter', {
data: function() {
return {
count: 0
}
},
template: `
<button @click="count++">
你点击了 {{ count }} 次
</button>
`
})
<div id="app">
<!-- 像使用HTML元素一样使用组件 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script>
new Vue({
el: '#app'
});
</script>
data必须是一个函数,而不是一个对象。这样可以确保每个实例都有自己独立的数据副本。
// 在任何Vue实例中都可以使用
Vue.component('my-component', {
// ... 选项 ...
});
优点:随处可用
缺点:增加构建体积
// 只在特定Vue实例中可用
const ComponentA = {
// ... 选项 ...
};
new Vue({
el: '#app',
components: {
'component-a': ComponentA
}
});
优点:按需加载
缺点:需要显式引入
Props是父组件向子组件传递数据的一种方式。子组件需要显式声明它期望接收的props:
<!-- 父组件模板 -->
<div id="blog-post-demo">
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
:content="post.content"
:published="post.published"
@publish="handlePublish"
></blog-post>
</div>
// 子组件定义
Vue.component('blog-post', {
// 声明props
props: {
title: String,
content: String,
published: Boolean
},
template: `
<div class="blog-post">
<h3>{{ title }}</h3>
<p>{{ content }}</p>
<button v-if="!published" @click="$emit('publish')">
发布文章
</button>
<span v-else class="badge bg-success">已发布</span>
</div>
`
});
// 父组件
new Vue({
el: '#blog-post-demo',
data: {
posts: [
{ id: 1, title: 'Vue.js入门', content: '...', published: true },
{ id: 2, title: '组件化开发', content: '...', published: false },
{ id: 3, title: 'Vue Router', content: '...', published: false }
]
},
methods: {
handlePublish() {
alert('文章已发布!');
}
}
});
| Props验证类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | title: String |
必须是字符串类型 |
| 数字 | age: Number |
必须是数字类型 |
| 布尔值 | isActive: Boolean |
必须是布尔类型 |
| 数组 | items: Array |
必须是数组类型 |
| 对象 | user: Object |
必须是对象类型 |
| 函数 | callback: Function |
必须是函数类型 |
| 自定义验证 |
|
自定义验证规则 |
子组件可以使用$emit触发自定义事件,父组件可以监听这些事件:
<!-- 父组件模板 -->
<div id="counter-app">
<p>总点击次数: {{ totalCount }}</p>
<button-counter
@increment="incrementTotal"
@reset="resetTotal"
></button-counter>
</div>
// 子组件
Vue.component('button-counter', {
data() {
return {
count: 0
};
},
template: `
<div>
<button @click="increment">
点击了 {{ count }} 次
</button>
<button @click="reset" class="btn btn-secondary">
重置
</button>
</div>
`,
methods: {
increment() {
this.count++;
// 触发自定义事件
this.$emit('increment');
},
reset() {
this.count = 0;
// 触发自定义事件并传递数据
this.$emit('reset', this.count);
}
}
});
// 父组件
new Vue({
el: '#counter-app',
data: {
totalCount: 0
},
methods: {
incrementTotal() {
this.totalCount++;
},
resetTotal(newCount) {
this.totalCount = 0;
console.log('计数器重置为:', newCount);
}
}
});
插槽允许父组件向子组件传递内容:
<!-- 子组件 -->
<div class="alert">
<slot>默认内容</slot>
</div>
<!-- 父组件使用 -->
<custom-alert>
这是自定义警告内容
</custom-alert>
<!-- 子组件 -->
<div class="card">
<div class="card-header">
<slot name="header"></slot>
</div>
<div class="card-body">
<slot name="body"></slot>
</div>
</div>
<!-- 父组件使用 -->
<custom-card>
<template v-slot:header>
<h4>卡片标题</h4>
</template>
<template v-slot:body>
<p>卡片内容...</p>
</template>
</custom-card>
<!-- 作用域插槽 -->
<!-- 子组件传递数据给插槽 -->
<ul>
<li v-for="item in items">
<slot :item="item">
{{ item.name }}
</slot>
</li>
</ul>
<!-- 父组件接收数据 -->
<item-list :items="items">
<template v-slot:default="slotProps">
<span class="text-primary">{{ slotProps.item.name }}</span>
</template>
</item-list>
<!-- 简写 -->
<item-list :items="items">
<template #default="{ item }">
<span class="text-success">{{ item.name }}</span>
</template>
</item-list>
使用<component>元素和is特性可以实现动态组件:
<div id="dynamic-component-demo">
<!-- 切换按钮 -->
<button @click="currentTab = 'tab-home'"
:class="{ active: currentTab === 'tab-home' }">
首页
</button>
<button @click="currentTab = 'tab-posts'"
:class="{ active: currentTab === 'tab-posts' }">
文章
</button>
<button @click="currentTab = 'tab-archive'"
:class="{ active: currentTab === 'tab-archive' }">
归档
</button>
<!-- 动态组件 -->
<component :is="currentTab" class="tab"></component>
</div>
Vue.component('tab-home', {
template: '<div>Home component</div>'
});
Vue.component('tab-posts', {
template: '<div>Posts component</div>'
});
Vue.component('tab-archive', {
template: '<div>Archive component</div>'
});
new Vue({
el: '#dynamic-component-demo',
data: {
currentTab: 'tab-home'
}
});
每个Vue组件实例在创建时都要经历一系列的初始化过程:
Vue.component('lifecycle-demo', {
data() {
return {
message: 'Hello Vue!'
};
},
// 生命周期钩子
beforeCreate() {
console.log('beforeCreate: 实例初始化之后,数据观测之前');
},
created() {
console.log('created: 实例创建完成,数据观测已建立');
// 可以在这里发起API请求
},
beforeMount() {
console.log('beforeMount: 挂载开始之前');
},
mounted() {
console.log('mounted: 挂载完成,DOM已渲染');
// 可以在这里操作DOM
},
beforeUpdate() {
console.log('beforeUpdate: 数据更新时,DOM更新之前');
},
updated() {
console.log('updated: 数据更新后,DOM已更新');
},
beforeDestroy() {
console.log('beforeDestroy: 实例销毁之前');
// 清理定时器、取消事件监听等
},
destroyed() {
console.log('destroyed: 实例销毁完成');
},
template: '<div>{{ message }}</div>'
});
使用组件构建一个完整的待办事项应用:
<div id="todo-app">
<h2>我的待办事项</h2>
<!-- 添加待办事项组件 -->
<todo-form @add-todo="addTodo"></todo-form>
<!-- 待办事项列表组件 -->
<todo-list
:todos="todos"
@toggle-todo="toggleTodo"
@delete-todo="deleteTodo"
></todo-list>
<!-- 统计信息组件 -->
<todo-stats :todos="todos"></todo-stats>
</div>
// 1. 待办事项表单组件
Vue.component('todo-form', {
data() {
return {
newTodo: '',
priority: 'medium'
};
},
template: `
<form @submit.prevent="addTodo" class="mb-4">
<div class="input-group">
<input
type="text"
v-model="newTodo"
placeholder="输入新的待办事项"
class="form-control"
>
<select v-model="priority" class="form-select">
<option value="low">低优先级</option>
<option value="medium">中优先级</option>
<option value="high">高优先级</option>
</select>
<button type="submit" class="btn btn-primary">
添加
</button>
</div>
</form>
`,
methods: {
addTodo() {
if (this.newTodo.trim()) {
this.$emit('add-todo', {
text: this.newTodo,
priority: this.priority
});
this.newTodo = '';
this.priority = 'medium';
}
}
}
});
// 2. 待办事项项组件
Vue.component('todo-item', {
props: {
todo: Object
},
template: `
<div class="todo-item" :class="{
'completed': todo.completed,
'priority-high': todo.priority === 'high',
'priority-low': todo.priority === 'low'
}">
<input
type="checkbox"
:checked="todo.completed"
@change="$emit('toggle', todo.id)"
>
<span class="todo-text">{{ todo.text }}</span>
<span class="badge bg-secondary">{{ todo.priority }}</span>
<button @click="$emit('delete', todo.id)" class="btn btn-sm btn-danger">
删除
</button>
</div>
`
});
// 3. 待办事项列表组件
Vue.component('todo-list', {
props: {
todos: Array
},
template: `
<div class="todo-list">
<div v-if="todos.length === 0" class="alert alert-info">
暂无待办事项
</div>
<todo-item
v-for="todo in todos"
:key="todo.id"
:todo="todo"
@toggle="$emit('toggle-todo', $event)"
@delete="$emit('delete-todo', $event)"
></todo-item>
</div>
`
});
// 4. 统计信息组件
Vue.component('todo-stats', {
props: {
todos: Array
},
computed: {
total() {
return this.todos.length;
},
completed() {
return this.todos.filter(todo => todo.completed).length;
},
pending() {
return this.total - this.completed;
}
},
template: `
<div class="todo-stats mt-4">
<div class="row">
<div class="col-md-4">
<div class="card bg-primary text-white">
<div class="card-body text-center">
<h4>{{ total }}</h4>
<p>总任务数</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card bg-success text-white">
<div class="card-body text-center">
<h4>{{ completed }}</h4>
<p>已完成</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card bg-warning text-white">
<div class="card-body text-center">
<h4>{{ pending }}</h4>
<p>待完成</p>
</div>
</div>
</div>
</div>
</div>
`
});
// 5. 主应用
new Vue({
el: '#todo-app',
data: {
todos: [
{ id: 1, text: '学习Vue组件', completed: true, priority: 'high' },
{ id: 2, text: '编写文档', completed: false, priority: 'medium' },
{ id: 3, text: '测试应用', completed: false, priority: 'low' }
],
nextId: 4
},
methods: {
addTodo(todoData) {
this.todos.push({
id: this.nextId++,
text: todoData.text,
completed: false,
priority: todoData.priority
});
},
toggleTodo(todoId) {
const todo = this.todos.find(t => t.id === todoId);
if (todo) {
todo.completed = !todo.completed;
}
},
deleteTodo(todoId) {
this.todos = this.todos.filter(t => t.id !== todoId);
}
}
});
每个组件应该只做一件事。如果一个组件变得过于复杂,应该考虑拆分成更小的组件。
Props向下传递,事件向上传递。避免直接修改子组件的状态。
通过Props和插槽使组件更加灵活,可以适应不同的使用场景。
始终为Props提供验证,这有助于捕获错误并提高代码可维护性。