跳到主要内容

Vue脚手架与组件开发详解:从入门到实践

阅读需 11 分钟
wqz

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组件可以被多次使用

学习建议

  1. 循序渐进:先掌握Vue基础,再学习Vue CLI和组件化开发
  2. 动手实践:创建自己的项目,应用所学知识
  3. 组件思维:学会将界面拆分为组件,思考组件之间的关系和通信
  4. 阅读文档:Vue官方文档详细介绍了组件的各种高级特性
  5. 学习开源项目:分析优秀的Vue项目,学习组件设计和代码组织方式

总结

Vue脚手架和组件化开发是构建现代Vue应用的基础。通过Vue CLI,我们可以快速创建一个配置完善的Vue项目;通过组件化开发,我们可以将应用拆分为多个独立、可复用的组件,使代码更加清晰、易于维护。

本文介绍了Vue脚手架的使用方法和组件化开发的核心概念,包括单文件组件、组件通信、生命周期、插槽等。掌握这些知识,你就能够开始构建结构清晰、可维护的Vue应用了。

参考资源

分享这篇文章
Loading Comments...