Vue脚手架和组件化开发是构建现代Vue应用的基础。本文将以简单易懂的语言,详细介绍Vue脚手架的使用方法和组件化开发的核心概念,帮助你快速掌握Vue应用开发的基本技能。
什么是Vue脚手架?
Vue脚手架(Vue CLI)是一个官方提供的工具,用于快速搭建Vue项目。它预先配置了许多常用工具(如webpack、Babel、ESLint等),让你可以直接专注于编写应用,而不必花时间在繁琐的配置上。
简单来说,Vue脚手架就像是一个"项目模板生成器",帮你搭好项目的基本框架,省去了从零开始的麻烦。
1. 安装和使用Vue CLI
1.1 安装Vue CLI
首先,你需要安装Node.js(包含npm),然后通过npm全局安装Vue CLI:
# 安装Vue CLI
npm install -g @vue/cli
# 检查安装版本
vue --version
1.2 创建新项目
安装完成后,你可以使用以下命令创建一个新项目:
# 创建一个新项目
vue create my-project
# 或者使用图形界面
vue ui
执 行vue create命令后,你会看到一些选项:
- 默认配置:包含基本的Babel和ESLint设置
- 手动选择特性:可以自定义项目需要的功能
如果选择手动配置,你可以选择:
- Babel(ES6转换器)
- TypeScript
- Progressive Web App (PWA) 支持
- Router(路由)
- Vuex(状态管理)
- CSS预处理器(如SCSS、Less)
- Linter / Formatter(代码检查和格式化)
- 单元测试
- E2E测试
1.3 项目结构
创建完成后,你会得到一个包含以下结构的项目:
my-project/
├── node_modules/ # 项目依赖
├── public/ # 静态资源
│ ├── favicon.ico # 网站图标
│ └── index.html # HTML模板
├── src/ # 源代码
│ ├── assets/ # 资源文件(图片、样式等)
│ ├── components/ # Vue组件
│ ├── views/ # 页面级组件(如果选择了Router)
│ ├── App.vue # 根组件
│ ├── main.js # 入口文件
│ ├── router.js # 路由配置(如果选择了Router)
│ └── store.js # Vuex配置(如果选择了Vuex)
├── .gitignore # Git忽略文件
├── babel.config.js # Babel配置
├── package.json # 项目信息和依赖
└── README.md # 项目说明
1.4 常用命令
# 启动开发服务器(带热重载)
npm run serve
# 构建生产版本
npm run build
# 代码检查和修复
npm run lint
2. 单文件组件(.vue文件)
Vue CLI项目使用单文件组件(Single File Components,简称SFC)。这是一种特殊的文件格式,扩展名为.vue,将组件的模板、脚本和样式封装在同一个文件中。
2.1 单文件组件的基本结构
<template>
<!-- HTML模板 -->
<div class="hello">
<h1>{{ msg }}</h1>
<button @click="count++">点击了 {{ count }} 次</button>
</div>
</template>
<script>
// JavaScript代码
export default {
name: 'HelloWorld',
props: {
msg: String
},
data() {
return {
count: 0
}
}
}
</script>
<style scoped>
/* CSS样式 */
h1 {
color: #42b983;
}
.hello {
padding: 20px;
}
</style>
单文件组件的三个部分:
<template>:包含组件的HTML模板<script>:包含组件的JavaScript代码<style>:包含组件的CSS样式
2.2 组件的导出和导入
在单文件组件中,我们通过export default导出组件:
export default {
name: 'HelloWorld',
// 组件选项...
}
在其他文件中,我们可以导入并使用这个组件:
import HelloWorld from './components/HelloWorld.vue'
export default {
components: {
HelloWorld
}
}
然后在模板中使用:
<template>
<div>
<HelloWorld msg="欢迎使用Vue"/>
</div>
</template>
3. 组件的基本构成
3.1 组件注册
全局注册
在main.js中全局注册组件:
import Vue from 'vue'
import App from './App.vue'
import GlobalComponent from './components/GlobalComponent.vue'
// 全局注册组件
Vue.component('global-component', GlobalComponent)
new Vue({
render: h => h(App)
}).$mount('#app')
全局注册的组件可以在任何其他组件中使用,无需再次导入。
局部注册
在单个组件中局部注册:
import LocalComponent from './components/LocalComponent.vue'
export default {
components: {
LocalComponent
}
}
局部注册的组件只能在当前组件中使用。
3.2 组件通信
Props(父传子)
父组件向子组件传递数据:
<!-- 父组件 -->
<template>
<div>
<child-component :message="parentMessage" :user="user"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: {
ChildComponent
},
data() {
return {
parentMessage: '来自父组件的消息',
user: { name: '张三', age: 25 }
}
}
}
</script>
<!-- 子组件 (ChildComponent.vue) -->
<template>
<div>
<p>{{ message }}</p>
<p>用户名: {{ user.name }}</p>
</div>
</template>
<script>
export default {
props: {
message: String,
user: Object
}
}
</script>
Props验证:
export default {
props: {
// 基础类型检查
propA: Number,
// 多种类型
propB: [String, Number],
// 必填项
propC: {
type: String,
required: true
},
// 带默认值
propD: {
type: Number,
default: 100
},
// 对象默认值
propE: {
type: Object,
default: function() {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function(value) {
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
}
自定义事件( 子传父)
子组件向父组件传递数据:
<!-- 子组件 -->
<template>
<div>
<button @click="sendToParent">发送到父组件</button>
</div>
</template>
<script>
export default {
methods: {
sendToParent() {
this.$emit('my-event', '这是来自子组件的数据');
}
}
}
</script>
<!-- 父组件 -->
<template>
<div>
<child-component @my-event="handleEvent"></child-component>
<p>收到的消息: {{ message }}</p>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: {
ChildComponent
},
data() {
return {
message: ''
}
},
methods: {
handleEvent(data) {
this.message = data;
}
}
}
</script>
事件总线(非父子组件通信)
创建一个事件总线:
// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()
组件A发送事件:
import { EventBus } from './eventBus.js'
// 在方法中发送事件
methods: {
sendMessage() {
EventBus.$emit('custom-event', '这是要传递的数据')
}
}
组件B接收事件:
import { EventBus } from './eventBus.js'
// 在created或mounted生命周期钩子中监听事件
created() {
EventBus.$on('custom-event', data => {
console.log(data) // '这是要传递的数据'
// 处理数据...
})
},
// 记得在组件销毁前移除事件监听
beforeDestroy() {
EventBus.$off('custom-event')
}
Vuex(复杂状态管理)
对于更复杂的状态管理,可以使用Vuex(这将在Vue Router & Vuex笔记中详细介绍)。
3.3 插槽(Slots)
插槽允许父组件向子组件传递内容:
基本插槽
<!-- 子组件 (BaseCard.vue) -->
<template>
<div class="card">
<div class="card-header">
<slot name="header">默认标题</slot>
</div>
<div class="card-body">
<slot>默认内容</slot>
</div>
<div class="card-footer">
<slot name="footer">默认底部</slot>
</div>
</div>
</template>
<!-- 父组件 -->
<template>
<div>
<base-card>
<template v-slot:header>
<h3>自定义标题</h3>
</template>
<p>这是主要内容</p>
<template v-slot:footer>
<button>确定</button>
</template>
</base-card>
</div>
</template>
<script>
import BaseCard from './BaseCard.vue'
export default {
components: {
BaseCard
}
}
</script>
作用域插槽
作用域插槽允许子组件向父组件传递数据:
<!-- 子组件 (UserList.vue) -->
<template>
<ul>
<li v-for="(user, index) in users" :key="index">
<slot :user="user" :index="index">
{{ user.name }}
</slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
users: [
{ name: '张三', age: 25 },
{ name: '李四', age: 30 },
{ name: '王五', age: 35 }
]
}
}
}
</script>
<!-- 父组件 -->
<template>
<div>
<user-list>
<template v-slot:default="slotProps">
<div class="user-item">
<strong>{{ slotProps.index + 1 }}. {{ slotProps.user.name }}</strong>
<em>({{ slotProps.user.age }}岁)</em>
</div>
</template>
</user-list>
</div>
</template>
<script>
import UserList from './UserList.vue'
export default {
components: {
UserList
}
}
</script>
4. 组件生命周期
每个Vue组件都有一个生命周期,从创建到销毁,可以在不同阶段执行代码:
export default {
// 创建前
beforeCreate() {
console.log('beforeCreate: 组件实例刚被创建,数据观测和事件配置之前');
},
// 创建后
created() {
console.log('created: 组件实例创建完成,可以访问数据和方法');
// 适合进行API调用获取初始数据
},
// 挂载前
beforeMount() {
console.log('beforeMount: 模板编译完成,即将挂载到DOM');
},
// 挂载后
mounted() {
console.log('mounted: 组件已挂载到DOM,可以访问DOM元素');
// 适合进行DOM操作或第三方库初始化
},
// 更新前
beforeUpdate() {
console.log('beforeUpdate: 数据更新,DOM更新之前');
},
// 更新后
updated() {
console.log('updated: DOM已根据最新数据更新');
// 注意避免在这里修改数据,可能导致无限循环
},
// 销毁前
beforeDestroy() {
console.log('beforeDestroy: 组件即将销毁');
// 适合清理定时器、事件监听等资源
},
// 销毁后
destroyed() {
console.log('destroyed: 组件已销毁');
}
}
生命周期钩子的常见用途:
- created: 获取初始数据、设置数据监听
- mounted: 访问DOM元素、初始化第三方库
- updated: 响应数据变化后的DOM更新
- beforeDestroy: 清理资源(定时器、事件监听等)
5. 组件复用与混入(Mixins)
5.1 组件复用
创建可复用的组件是Vue的核心理念之一。一个好的可复用组件应该:
- 职责单一
- 接口清晰(props和events)
- 尽量独立
例如,一个可复用的按钮组件:
<!-- BaseButton.vue -->
<template>
<button
:class="['base-btn', `base-btn--${type}`, {'base-btn--disabled': disabled}]"
:disabled="disabled"
@click="onClick"
>
<slot></slot>
</button>
</template>
<script>
export default {
name: 'BaseButton',
props: {
type: {
type: String,
default: 'default',
validator: value => ['default', 'primary', 'danger'].includes(value)
},
disabled: {
type: Boolean,
default: false
}
},
methods: {
onClick(event) {
if (!this.disabled) {
this.$emit('click', event);
}
}
}
}
</script>
<style scoped>
.base-btn {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.base-btn--default {
background-color: #f5f5f5;
border: 1px solid #d9d9d9;
color: #333;
}
.base-btn--primary {
background-color: #1890ff;
border: 1px solid #1890ff;
color: white;
}
.base-btn--danger {
background-color: #ff4d4f;
border: 1px solid #ff4d4f;
color: white;
}
.base-btn--disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
使用这个按钮组件:
<template>
<div>
<base-button>默认按钮</base-button>
<base-button type="primary" @click="handleClick">主要按钮</base-button>
<base-button type="danger" :disabled="true">禁用的危险按钮</base-button>
</div>
</template>
<script>
import BaseButton from './BaseButton.vue'
export default {
components: {
BaseButton
},
methods: {
handleClick() {
console.log('按钮被点击了')
}
}
}
</script>
5.2 混入(Mixins)
混入是一种分发Vue组件中可复用功能的方式。混入对象可以包含任何组件选项。
创建一个混入:
// myMixin.js
export const myMixin = {
data() {
return {
mixinData: 'Hello from mixin'
}
},
created() {
console.log('Mixin created hook called')
},
methods: {
mixinMethod() {
console.log('Method from mixin')
}
}
}
使用混入:
<template>
<div>
<p>{{ mixinData }}</p>
<button @click="mixinMethod">调用混入方法</button>
</div>
</template>
<script>
import { myMixin } from './myMixin.js'
export default {
mixins: [myMixin],
created() {
console.log('Component created hook called')
// 输出顺序:
// 1. "Mixin created hook called"
// 2. "Component created hook called"
}
}
</script>
混入的合并规则:
- 数据对象在内部会进行递归合并,组件数据优先
- 同名钩子函数将合并为一个数组,混入的钩子先调用
- 值为对象的选项(如methods、components等)将合并为同一个对象,组件对象优先
6. 实战示例:待办事项应用
下面是一个使用Vue CLI和组件化开发的待办事项应用示例:
项目结构
todo-app/
├── src/
│ ├── assets/
│ ├── components/
│ │ ├── TodoForm.vue
│ │ ├── TodoItem.vue
│ │ └── TodoList.vue
│ ├── App.vue
│ └── main.js
└─ ─ ...
组件实现
App.vue (根组件)
<template>
<div id="app">
<h1>待办事项应用</h1>
<todo-form @add-todo="addTodo"></todo-form>
<todo-list
:todos="todos"
@toggle-todo="toggleTodo"
@delete-todo="deleteTodo"
></todo-list>
</div>
</template>
<script>
import TodoForm from './components/TodoForm.vue'
import TodoList from './components/TodoList.vue'
export default {
name: 'App',
components: {
TodoForm,
TodoList
},
data() {
return {
todos: [
{ id: 1, text: '学习Vue基础', completed: true },
{ id: 2, text: '学习Vue CLI', completed: false },
{ id: 3, text: '创建一个项目', completed: false }
],
nextId: 4
}
},
methods: {
addTodo(text) {
this.todos.push({
id: this.nextId++,
text,
completed: false
})
},
toggleTodo(id) {
const todo = this.todos.find(todo => todo.id === id)
if (todo) {
todo.completed = !todo.completed
}
},
deleteTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id)
}
}
}
</script>
这个待办事项应用展示了Vue组件化开发的核心概念:
- 组件拆分:将应用拆分为多个独立组件
- 组件通信:使用props向下传递数据,使用事件向上传递操作
- 单一职责:每个组件只负责特定功能
- 可复用性:TodoItem组件可以被多次使用