Vue.js常用语法完全指南:从基础到高级应用

Vue.js常用语法完全指南:从基础到高级应用

Vue.js是一个渐进式JavaScript框架,以其简洁的语法和强大的功能而受到开发者的喜爱。本指南将全面介绍Vue.js的常用语法,从基础概念到高级应用,帮助你快速掌握Vue.js开发。

目录

  1. Vue.js基础概念
  2. 模板语法
  3. 指令系统
  4. 组件开发
  5. 状态管理
  6. 生命周期钩子
  7. 路由管理
  8. 实际应用案例
  9. 最佳实践

Vue.js基础概念

什么是Vue.js

Vue.js是一个用于构建用户界面的渐进式框架。它的核心库只关注视图层,易于上手,也便于与第三方库或既有项目整合。

核心特性

  • 响应式数据绑定:数据变化时自动更新视图
  • 组件化开发:可复用的组件结构
  • 虚拟DOM:高效的DOM更新机制
  • 指令系统:声明式的DOM操作
  • 单文件组件.vue文件整合模板、脚本和样式

模板语法

插值表达式

Vue使用双大括号语法进行文本插值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<span>{{ count }}</span>
</div>
</template>

<script>
export default {
data() {
return {
title: 'Vue.js指南',
message: '欢迎学习Vue.js',
count: 0
}
}
}
</script>

原始HTML

使用v-html指令插入原始HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div v-html="rawHtml"></div>
</template>

<script>
export default {
data() {
return {
rawHtml: '<span style="color: red;">这是红色文字</span>'
}
}
}
</script>

属性绑定

使用v-bind或简写:绑定属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div>
<!-- 完整写法 -->
<img v-bind:src="imageSrc" v-bind:alt="imageAlt">

<!-- 简写 -->
<img :src="imageSrc" :alt="imageAlt">

<!-- 动态类名 -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>

<!-- 动态样式 -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
</div>
</template>

<script>
export default {
data() {
return {
imageSrc: '/path/to/image.jpg',
imageAlt: '示例图片',
isActive: true,
hasError: false,
activeColor: 'red',
fontSize: 14
}
}
}
</script>

指令系统

条件渲染

v-if / v-else-if / v-else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<h1 v-if="type === 'A'">类型A</h1>
<h1 v-else-if="type === 'B'">类型B</h1>
<h1 v-else>其他类型</h1>

<!-- 使用template包装多个元素 -->
<template v-if="showDetails">
<p>详细信息1</p>
<p>详细信息2</p>
</template>
</div>
</template>

<script>
export default {
data() {
return {
type: 'A',
showDetails: true
}
}
}
</script>

v-show

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<h1 v-show="isVisible">这个标题可能隐藏</h1>
<button @click="toggleVisibility">切换显示</button>
</div>
</template>

<script>
export default {
data() {
return {
isVisible: true
}
},
methods: {
toggleVisibility() {
this.isVisible = !this.isVisible
}
}
}
</script>

v-if vs v-show 的区别:

  • v-if:条件性地渲染元素,不满足条件时元素不存在于DOM中
  • v-show:元素始终渲染,通过CSS的display属性控制显示/隐藏

列表渲染

v-for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<template>
<div>
<!-- 基本用法 -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>

<!-- 带索引 -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
</ul>

<!-- 遍历对象 -->
<ul>
<li v-for="(value, key) in user" :key="key">
{{ key }}: {{ value }}
</li>
</ul>

<!-- 遍历数字 -->
<span v-for="n in 10" :key="n">{{ n }} </span>
</div>
</template>

<script>
export default {
data() {
return {
items: [
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' },
{ id: 3, name: '项目3' }
],
user: {
name: '张三',
age: 25,
email: 'zhangsan@example.com'
}
}
}
}
</script>

事件处理

v-on

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<template>
<div>
<!-- 基本事件 -->
<button v-on:click="handleClick">点击我</button>

<!-- 简写 -->
<button @click="handleClick">点击我</button>

<!-- 内联处理器 -->
<button @click="count++">计数: {{ count }}</button>

<!-- 事件修饰符 -->
<form @submit.prevent="handleSubmit">
<input @keyup.enter="handleEnter">
</form>

<!-- 按键修饰符 -->
<input @keyup.enter="handleEnter" @keyup.esc="handleEsc">

<!-- 鼠标修饰符 -->
<div @click.right="handleRightClick">右键点击</div>

<!-- 事件对象 -->
<button @click="handleClickWithEvent($event)">带事件对象</button>
</div>
</template>

<script>
export default {
data() {
return {
count: 0
}
},
methods: {
handleClick() {
console.log('按钮被点击')
},
handleSubmit() {
console.log('表单提交')
},
handleEnter() {
console.log('按下了回车键')
},
handleEsc() {
console.log('按下了ESC键')
},
handleRightClick() {
console.log('右键点击')
},
handleClickWithEvent(event) {
console.log('事件对象:', event)
}
}
}
</script>

表单输入绑定

v-model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<template>
<div>
<!-- 文本输入 -->
<input v-model="message" placeholder="请输入消息">
<p>消息: {{ message }}</p>

<!-- 多行文本 -->
<textarea v-model="multilineText"></textarea>
<p>多行文本: {{ multilineText }}</p>

<!-- 复选框 -->
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

<!-- 多个复选框 -->
<div>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<p>选中: {{ checkedNames }}</p>
</div>

<!-- 单选按钮 -->
<div>
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<p>选中: {{ picked }}</p>
</div>

<!-- 选择框 -->
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>选中: {{ selected }}</p>
</div>
</template>

<script>
export default {
data() {
return {
message: '',
multilineText: '',
checked: false,
checkedNames: [],
picked: '',
selected: ''
}
}
}
</script>

修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div>
<!-- .lazy - 在change事件中同步 -->
<input v-model.lazy="msg">

<!-- .number - 自动转换为数字 -->
<input v-model.number="age" type="number">

<!-- .trim - 自动过滤首尾空白字符 -->
<input v-model.trim="msg">

<!-- 事件修饰符 -->
<div @click.stop="doThis">阻止冒泡</div>
<div @click.prevent="doThis">阻止默认行为</div>
<div @click.once="doThis">只触发一次</div>

<!-- 按键修饰符 -->
<input @keyup.enter="submit">
<input @keyup.tab="nextField">
<input @keyup.delete="confirmDelete">

<!-- 系统修饰键 -->
<input @keyup.ctrl.67="copy">
<input @keyup.alt.enter="submit">
</div>
</template>

组件开发

组件基础

组件注册

1
2
3
4
5
6
7
8
9
10
11
12
13
// 全局注册
Vue.component('my-component', {
template: '<div>我的组件</div>'
})

// 局部注册
export default {
components: {
'my-component': {
template: '<div>我的组件</div>'
}
}
}

单文件组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!-- MyComponent.vue -->
<template>
<div class="my-component">
<h2>{{ title }}</h2>
<p>{{ content }}</p>
<button @click="handleClick">点击</button>
</div>
</template>

<script>
export default {
name: 'MyComponent',
props: {
title: {
type: String,
required: true
},
content: {
type: String,
default: '默认内容'
}
},
data() {
return {
count: 0
}
},
methods: {
handleClick() {
this.count++
this.$emit('clicked', this.count)
}
}
}
</script>

<style scoped>
.my-component {
border: 1px solid #ccc;
padding: 20px;
margin: 10px;
}
</style>

Props

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template>
<div>
<h3>{{ title }}</h3>
<p>用户: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
<p>状态: {{ isActive ? '活跃' : '非活跃' }}</p>
</div>
</template>

<script>
export default {
props: {
// 基础类型检查
title: String,

// 多种可能的类型
value: [String, Number],

// 必填的字符串
required: {
type: String,
required: true
},

// 带有默认值的数字
count: {
type: Number,
default: 0
},

// 带有默认值的对象
user: {
type: Object,
default: () => ({
name: '匿名',
age: 0
})
},

// 自定义验证函数
isActive: {
validator: function (value) {
return typeof value === 'boolean'
}
}
}
}
</script>

自定义事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- 子组件 -->
<template>
<div>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
<p>当前值: {{ value }}</p>
</div>
</template>

<script>
export default {
props: {
value: {
type: Number,
default: 0
}
},
methods: {
increment() {
this.$emit('update:value', this.value + 1)
},
decrement() {
this.$emit('update:value', this.value - 1)
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- 父组件 -->
<template>
<div>
<counter v-model:value="count" @update:value="handleUpdate" />
<p>父组件中的值: {{ count }}</p>
</div>
</template>

<script>
import Counter from './Counter.vue'

export default {
components: {
Counter
},
data() {
return {
count: 0
}
},
methods: {
handleUpdate(newValue) {
console.log('值更新为:', newValue)
}
}
}
</script>

插槽

基本插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 子组件 -->
<template>
<div class="card">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 父组件 -->
<template>
<card>
<template #header>
<h2>卡片标题</h2>
</template>

<p>这是卡片的主要内容</p>

<template #footer>
<button>确定</button>
</template>
</card>
</template>

作用域插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 子组件 -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="index">
{{ item.name }}
</slot>
</li>
</ul>
</template>

<script>
export default {
props: {
items: Array
}
}
</script>
1
2
3
4
5
6
7
8
9
<!-- 父组件 -->
<template>
<item-list :items="users">
<template #default="{ item, index }">
<strong>{{ index + 1 }}. {{ item.name }}</strong>
<span class="age">({{ item.age }}岁)</span>
</template>
</item-list>
</template>

状态管理

计算属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<div>
<p>原始消息: {{ message }}</p>
<p>反转消息: {{ reversedMessage }}</p>
<p>字符数: {{ messageLength }}</p>
</div>
</template>

<script>
export default {
data() {
return {
message: 'Hello Vue!'
}
},
computed: {
// 基本计算属性
reversedMessage() {
return this.message.split('').reverse().join('')
},

// 带getter和setter的计算属性
fullName: {
get() {
return this.firstName + ' ' + this.lastName
},
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
},

// 依赖多个数据
messageLength() {
return this.message.length
}
}
}
</script>

侦听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template>
<div>
<input v-model="question" placeholder="输入问题">
<p>{{ answer }}</p>
</div>
</template>

<script>
export default {
data() {
return {
question: '',
answer: '请输入问题'
}
},
watch: {
// 基本侦听
question(newQuestion, oldQuestion) {
this.answer = '等待输入...'
this.getAnswer()
},

// 深度侦听对象
user: {
handler(newVal, oldVal) {
console.log('用户信息变化')
},
deep: true
},

// 立即执行
immediate: true,

// 对象属性的侦听
'user.name'(newName, oldName) {
console.log('用户名变化:', newName)
}
},
methods: {
getAnswer() {
// 模拟API调用
setTimeout(() => {
this.answer = '这是答案'
}, 1000)
}
}
}
</script>

Vuex状态管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// store/index.js
import { createStore } from 'vuex'

export default createStore({
state: {
count: 0,
user: null
},
mutations: {
increment(state) {
state.count++
},
setUser(state, user) {
state.user = user
}
},
actions: {
increment({ commit }) {
commit('increment')
},
async fetchUser({ commit }, userId) {
const user = await api.getUser(userId)
commit('setUser', user)
}
})

生命周期钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<template>
<div>
<h1>{{ title }}</h1>
<p>组件已渲染</p>
</div>
</template>

<script>
export default {
data() {
return {
title: '生命周期示例'
}
},

// 创建阶段
beforeCreate() {
console.log('beforeCreate: 实例刚创建,数据观测和事件配置之前')
},

created() {
console.log('created: 实例创建完成,数据观测和事件配置完成')
// 适合进行数据初始化
},

// 挂载阶段
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: 实例销毁完成')
}
}
</script>

路由管理

Vue Router基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'

const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
},
{
path: '/user/:id',
name: 'User',
component: () => import('../views/User.vue'),
props: true
},
{
path: '/admin',
name: 'Admin',
component: () => import('../views/Admin.vue'),
meta: { requiresAuth: true }
}
]

const router = createRouter({
history: createWebHistory(),
routes
})

// 路由守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login')
} else {
next()
}
})

export default router

路由使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div>
<!-- 路由链接 -->
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
<router-link :to="{ name: 'User', params: { id: 123 }}">用户</router-link>

<!-- 路由视图 -->
<router-view></router-view>

<!-- 编程式导航 -->
<button @click="goToAbout">去关于页面</button>
</div>
</template>

<script>
export default {
methods: {
goToAbout() {
this.$router.push('/about')
// 或者
this.$router.push({ name: 'About' })
}
}
}
</script>

实际应用案例

案例1:待办事项应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
<!-- TodoApp.vue -->
<template>
<div class="todo-app">
<h1>待办事项</h1>

<!-- 添加新任务 -->
<form @submit.prevent="addTodo">
<input
v-model="newTodo"
placeholder="输入新任务"
class="todo-input"
>
<button type="submit">添加</button>
</form>

<!-- 任务列表 -->
<ul class="todo-list">
<li
v-for="todo in filteredTodos"
:key="todo.id"
class="todo-item"
:class="{ completed: todo.completed }"
>
<input
type="checkbox"
v-model="todo.completed"
class="todo-checkbox"
>
<span class="todo-text">{{ todo.text }}</span>
<button @click="removeTodo(todo.id)" class="delete-btn">删除</button>
</li>
</ul>

<!-- 过滤器 -->
<div class="filters">
<button
v-for="filter in filters"
:key="filter"
@click="currentFilter = filter"
:class="{ active: currentFilter === filter }"
>
{{ filter }}
</button>
</div>

<!-- 统计信息 -->
<div class="stats">
总计: {{ todos.length }} |
已完成: {{ completedCount }} |
未完成: {{ remainingCount }}
</div>
</div>
</template>

<script>
export default {
name: 'TodoApp',
data() {
return {
newTodo: '',
todos: [],
currentFilter: '全部',
filters: ['全部', '进行中', '已完成']
}
},
computed: {
filteredTodos() {
switch (this.currentFilter) {
case '进行中':
return this.todos.filter(todo => !todo.completed)
case '已完成':
return this.todos.filter(todo => todo.completed)
default:
return this.todos
}
},
completedCount() {
return this.todos.filter(todo => todo.completed).length
},
remainingCount() {
return this.todos.filter(todo => !todo.completed).length
}
},
methods: {
addTodo() {
if (this.newTodo.trim()) {
this.todos.push({
id: Date.now(),
text: this.newTodo,
completed: false
})
this.newTodo = ''
}
},
removeTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id)
}
}
}
</script>

<style scoped>
.todo-app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}

.todo-input {
width: 70%;
padding: 10px;
margin-right: 10px;
}

.todo-list {
list-style: none;
padding: 0;
}

.todo-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}

.todo-item.completed .todo-text {
text-decoration: line-through;
color: #999;
}

.todo-checkbox {
margin-right: 10px;
}

.todo-text {
flex: 1;
}

.delete-btn {
background: #ff4757;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
}

.filters {
margin: 20px 0;
}

.filters button {
margin-right: 10px;
padding: 5px 15px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
}

.filters button.active {
background: #007bff;
color: white;
}

.stats {
margin-top: 20px;
padding: 10px;
background: #f8f9fa;
border-radius: 5px;
}
</style>

案例2:用户管理系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
<!-- UserManagement.vue -->
<template>
<div class="user-management">
<h1>用户管理系统</h1>

<!-- 搜索和添加 -->
<div class="controls">
<input
v-model="searchQuery"
placeholder="搜索用户..."
class="search-input"
>
<button @click="showAddForm = true" class="add-btn">添加用户</button>
</div>

<!-- 用户列表 -->
<div class="user-list">
<div
v-for="user in filteredUsers"
:key="user.id"
class="user-card"
>
<div class="user-info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<p>角色: {{ user.role }}</p>
</div>
<div class="user-actions">
<button @click="editUser(user)" class="edit-btn">编辑</button>
<button @click="deleteUser(user.id)" class="delete-btn">删除</button>
</div>
</div>
</div>

<!-- 添加/编辑用户模态框 -->
<div v-if="showAddForm || editingUser" class="modal">
<div class="modal-content">
<h2>{{ editingUser ? '编辑用户' : '添加用户' }}</h2>
<form @submit.prevent="saveUser">
<div class="form-group">
<label>姓名:</label>
<input v-model="userForm.name" required>
</div>
<div class="form-group">
<label>邮箱:</label>
<input v-model="userForm.email" type="email" required>
</div>
<div class="form-group">
<label>角色:</label>
<select v-model="userForm.role">
<option value="admin">管理员</option>
<option value="user">普通用户</option>
<option value="guest">访客</option>
</select>
</div>
<div class="form-actions">
<button type="submit" class="save-btn">保存</button>
<button type="button" @click="cancelEdit" class="cancel-btn">取消</button>
</div>
</form>
</div>
</div>
</div>
</template>

<script>
export default {
name: 'UserManagement',
data() {
return {
searchQuery: '',
showAddForm: false,
editingUser: null,
users: [
{ id: 1, name: '张三', email: 'zhangsan@example.com', role: 'admin' },
{ id: 2, name: '李四', email: 'lisi@example.com', role: 'user' },
{ id: 3, name: '王五', email: 'wangwu@example.com', role: 'guest' }
],
userForm: {
name: '',
email: '',
role: 'user'
}
}
},
computed: {
filteredUsers() {
if (!this.searchQuery) return this.users
return this.users.filter(user =>
user.name.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
user.email.toLowerCase().includes(this.searchQuery.toLowerCase())
)
}
},
methods: {
editUser(user) {
this.editingUser = user
this.userForm = { ...user }
},
saveUser() {
if (this.editingUser) {
// 编辑用户
const index = this.users.findIndex(u => u.id === this.editingUser.id)
this.users.splice(index, 1, { ...this.userForm })
} else {
// 添加用户
this.users.push({
...this.userForm,
id: Date.now()
})
}
this.cancelEdit()
},
deleteUser(id) {
if (confirm('确定要删除这个用户吗?')) {
this.users = this.users.filter(user => user.id !== id)
}
},
cancelEdit() {
this.showAddForm = false
this.editingUser = null
this.userForm = { name: '', email: '', role: 'user' }
}
}
}
</script>

<style scoped>
.user-management {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}

.controls {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}

.search-input {
width: 300px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}

.add-btn {
background: #28a745;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
}

.user-list {
display: grid;
gap: 15px;
}

.user-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
}

.user-info h3 {
margin: 0 0 5px 0;
}

.user-info p {
margin: 5px 0;
color: #666;
}

.user-actions button {
margin-left: 10px;
padding: 5px 15px;
border: none;
border-radius: 3px;
cursor: pointer;
}

.edit-btn {
background: #007bff;
color: white;
}

.delete-btn {
background: #dc3545;
color: white;
}

.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}

.modal-content {
background: white;
padding: 30px;
border-radius: 8px;
width: 400px;
}

.form-group {
margin-bottom: 15px;
}

.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}

.form-group input,
.form-group select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}

.form-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
}

.save-btn {
background: #28a745;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}

.cancel-btn {
background: #6c757d;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
</style>

最佳实践

1. 组件设计原则

单一职责原则

每个组件应该只负责一个功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 好的设计:职责单一 -->
<template>
<div class="user-avatar">
<img :src="avatarUrl" :alt="userName">
</div>
</template>

<script>
export default {
name: 'UserAvatar',
props: {
avatarUrl: String,
userName: String
}
}
</script>

组件通信

  • 父传子:使用props
  • 子传父:使用$emit
  • 兄弟组件:使用事件总线或状态管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 父组件 -->
<template>
<div>
<child-component
:message="parentMessage"
@child-event="handleChildEvent"
/>
</div>
</template>

<script>
export default {
data() {
return {
parentMessage: '来自父组件的消息'
}
},
methods: {
handleChildEvent(data) {
console.log('子组件事件:', data)
}
}
}
</script>

2. 性能优化

使用key优化列表渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<!-- 好的做法:使用唯一key -->
<div v-for="item in items" :key="item.id">
{{ item.name }}
</div>

<!-- 避免:使用索引作为key -->
<div v-for="(item, index) in items" :key="index">
{{ item.name }}
</div>
</div>
</template>

计算属性缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
export default {
data() {
return {
items: [1, 2, 3, 4, 5]
}
},
computed: {
// 好的做法:使用计算属性
filteredItems() {
return this.items.filter(item => item > 2)
}
},
methods: {
// 避免:在模板中直接调用方法
getFilteredItems() {
return this.items.filter(item => item > 2)
}
}
}
</script>

异步组件加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 路由懒加载
const routes = [
{
path: '/about',
component: () => import('../views/About.vue')
}
]

// 组件懒加载
export default {
components: {
AsyncComponent: () => import('./AsyncComponent.vue')
}
}

3. 代码组织

文件结构

1
2
3
4
5
6
7
8
9
10
src/
├── components/ # 可复用组件
│ ├── common/ # 通用组件
│ └── business/ # 业务组件
├── views/ # 页面组件
├── router/ # 路由配置
├── store/ # 状态管理
├── utils/ # 工具函数
├── api/ # API接口
└── assets/ # 静态资源

组件命名规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 组件名使用PascalCase -->
<template>
<div>
<UserProfile />
<ProductCard />
<NavigationMenu />
</div>
</template>

<script>
// 文件名使用kebab-case
// UserProfile.vue
export default {
name: 'UserProfile' // 组件名使用PascalCase
}
</script>

4. 错误处理

全局错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// main.js
Vue.config.errorHandler = (err, vm, info) => {
console.error('全局错误:', err)
console.error('组件实例:', vm)
console.error('错误信息:', info)
}

// 组件内错误处理
export default {
methods: {
async fetchData() {
try {
const data = await api.getData()
this.data = data
} catch (error) {
console.error('获取数据失败:', error)
this.$message.error('数据加载失败')
}
}
}
}

5. 测试

单元测试示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// UserProfile.spec.js
import { shallowMount } from '@vue/test-utils'
import UserProfile from '@/components/UserProfile.vue'

describe('UserProfile', () => {
it('renders user name correctly', () => {
const wrapper = shallowMount(UserProfile, {
propsData: {
user: {
name: 'John Doe',
email: 'john@example.com'
}
}
})

expect(wrapper.find('.user-name').text()).toBe('John Doe')
})

it('emits update event when user is edited', async () => {
const wrapper = shallowMount(UserProfile)

await wrapper.find('.edit-btn').trigger('click')
await wrapper.find('.save-btn').trigger('click')

expect(wrapper.emitted().update).toBeTruthy()
})
})

总结

Vue.js作为现代前端框架,提供了丰富的功能和优雅的语法。通过本指南,我们学习了:

  1. 基础语法:模板语法、指令系统、事件处理
  2. 组件开发:组件注册、Props、事件、插槽
  3. 状态管理:计算属性、侦听器、Vuex
  4. 生命周期:组件的创建、挂载、更新、销毁过程
  5. 路由管理:Vue Router的使用和配置
  6. 实际应用:完整的待办事项和用户管理案例
  7. 最佳实践:性能优化、代码组织、错误处理

掌握这些知识后,你就可以开始构建复杂的Vue.js应用程序了。记住,实践是最好的学习方式,多动手编写代码,遇到问题时查阅官方文档,你会逐渐成为Vue.js专家。

参考资源


本文档涵盖了Vue.js的核心概念和常用语法,适合初学者和有一定经验的开发者参考。随着Vue.js的不断发展,建议关注官方文档获取最新信息。


Vue.js常用语法完全指南:从基础到高级应用
https://zuyue200.github.io/2025/10/11/vue-common-syntax-guide/
作者
zuyue200
发布于
2025年10月11日
更新于
2025年11月9日
许可协议