返回正文
Are you an LLM? You can read better optimized documentation at /zh-CN/blog/2025-10/vue-event.md for this page in Markdown format
Vue3 事件处理详解
前言
Vue3 的事件处理是组件交互的核心,但官方文档可能让初学者感到困惑。本文将以更直观的方式讲解 Vue3 事件处理,通过丰富的示例和详细注释,帮助你快速掌握这一重要概念。
一、事件处理基础
基本事件绑定
vue
<template>
<div>
<!-- 最简单的点击事件 -->
<button @click="count++">点击增加计数</button>
<p>当前计数: {{ count }}</p>
<!-- 使用方法处理事件 -->
<button @click="handleClick">点击我</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
// 事件处理方法
const handleClick = () => {
console.log('按钮被点击了!')
count.value++ // 注意:在setup语法糖中需要使用.value
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
核心概念解释:
@click
是v-on:click
的简写形式- 可以直接内联执行简单代码,如
count++
- 复杂逻辑建议使用方法处理
方法事件处理器
vue
<template>
<div>
<!-- 传递自定义参数 -->
<button @click="sayHello('Vue3')">打招呼</button>
<!-- 同时传递事件对象和自定义参数 -->
<button @click="handleEvent($event, '额外参数')">
带事件对象的点击
</button>
</div>
</template>
<script setup>
// 基本事件处理方法
const sayHello = (name) => {
alert(`你好,${name}!`)
}
// 接收事件对象和自定义参数
const handleEvent = (event, extraParam) => {
console.log('事件对象:', event)
console.log('事件类型:', event.type) // 输出: click
console.log('触发元素:', event.target) // 触发事件的DOM元素
console.log('额外参数:', extraParam)
// 阻止默认行为(如果适用)
event.preventDefault()
}
</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
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
二、事件修饰符详解
事件修饰符是 Vue 提供的特殊后缀,用于处理常见的 DOM 事件细节。
常用事件修饰符
vue
<template>
<div>
<!-- .stop - 阻止事件冒泡 -->
<div @click="parentClick">
父元素
<button @click.stop="childClick">子按钮(不会触发父元素点击)</button>
</div>
<!-- .prevent - 阻止默认行为 -->
<form @submit.prevent="onSubmit">
<input type="text" />
<button type="submit">提交(不会刷新页面)</button>
</form>
<!-- .once - 只触发一次 -->
<button @click.once="onceClick">这个按钮只能点击一次</button>
<!-- .self - 只有点击元素本身才触发 -->
<div @click.self="selfClick" style="padding: 20px; background: #f0f0f0;">
点击这个div的空白区域才触发
<button>点击按钮不会触发div的事件</button>
</div>
</div>
</template>
<script setup>
const parentClick = () => {
console.log('父元素被点击')
}
const childClick = () => {
console.log('子按钮被点击,但不会冒泡到父元素')
}
const onSubmit = () => {
console.log('表单提交,但阻止了默认的页面刷新行为')
}
const onceClick = () => {
console.log('这个日志只会出现一次!')
}
const selfClick = () => {
console.log('只有直接点击div区域才会触发')
}
</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
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
按键修饰符
vue
<template>
<div>
<!-- 按键事件处理 -->
<input
@keyup.enter="submitForm"
placeholder="按回车键提交"
/>
<input
@keyup.esc="clearInput"
placeholder="按ESC键清空"
/>
<!-- 组合按键 -->
<input
@keyup.ctrl.enter="specialSubmit"
placeholder="Ctrl + Enter 特殊提交"
/>
<!-- 系统修饰键 -->
<div
@click.ctrl="ctrlClick"
style="padding: 20px; border: 1px solid #ccc;"
>
按住Ctrl键点击我
</div>
</div>
</template>
<script setup>
const submitForm = () => {
console.log('回车键被按下,提交表单')
}
const clearInput = (event) => {
event.target.value = '' // 清空输入框
console.log('ESC键被按下,输入框已清空')
}
const specialSubmit = () => {
console.log('Ctrl + Enter 组合键被按下')
}
const ctrlClick = () => {
console.log('在按住Ctrl键的同时点击了元素')
}
</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
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
鼠标按键修饰符
vue
<template>
<div>
<!-- 鼠标按键修饰符 -->
<button @click.left="leftClick">左键点击</button>
<button @click.right="rightClick">右键点击</button>
<button @click.middle="middleClick">中键点击</button>
<!-- 右键点击并阻止默认上下文菜单 -->
<div
@click.right.prevent="customContextMenu"
style="padding: 20px; background: #e0e0e0;"
>
右键点击这里(不会出现浏览器默认菜单)
</div>
</div>
</template>
<script setup>
const leftClick = () => {
console.log('鼠标左键点击')
}
const rightClick = () => {
console.log('鼠标右键点击')
}
const middleClick = () => {
console.log('鼠标中键点击')
}
const customContextMenu = (event) => {
console.log('自定义右键菜单处理')
// 这里可以显示自定义的上下文菜单
alert('这是自定义的右键菜单!')
}
</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
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
三、高级事件处理技巧
动态事件名
vue
<template>
<div>
<!-- 动态事件名 -->
<button @[eventName]="dynamicHandler">动态事件按钮</button>
<!-- 切换事件类型 -->
<select v-model="eventName">
<option value="click">点击事件</option>
<option value="mouseover">鼠标悬停</option>
<option value="mouseleave">鼠标离开</option>
</select>
<p>当前事件类型: {{ eventName }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const eventName = ref('click')
const dynamicHandler = (event) => {
console.log(`动态事件被触发: ${event.type}`)
switch(event.type) {
case 'click':
console.log('处理点击逻辑')
break
case 'mouseover':
console.log('处理鼠标悬停逻辑')
break
case 'mouseleave':
console.log('处理鼠标离开逻辑')
break
}
}
</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
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
多个事件处理器
vue
<template>
<div>
<!-- 多个事件处理 -->
<button
@click="
firstHandler();
secondHandler($event);
"
>
点击我触发多个方法
</button>
<!-- 或者使用方法组合 -->
<button @click="combinedHandler">
组合方法处理
</button>
</div>
</template>
<script setup>
const firstHandler = () => {
console.log('第一个处理方法执行')
}
const secondHandler = (event) => {
console.log('第二个处理方法执行,事件对象:', event)
}
// 组合处理方法
const combinedHandler = (event) => {
firstHandler()
secondHandler(event)
console.log('额外的处理逻辑')
}
</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
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
事件传递(emit)详解
vue
<!-- 子组件 ChildComponent.vue -->
<template>
<div>
<button @click="handleButtonClick">
子组件按钮
</button>
<!-- 带参数的事件传递 -->
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
placeholder="双向绑定示例"
/>
</div>
</template>
<script setup>
// 定义组件可以发出的事件
const emit = defineEmits(['custom-event', 'update:modelValue'])
const handleButtonClick = () => {
// 发出自定义事件
emit('custom-event', { message: '来自子组件的数据', time: new Date() })
}
</script>
<!-- 父组件使用 -->
<template>
<div>
<ChildComponent
@custom-event="handleCustomEvent"
v-model:modelValue="inputValue"
/>
<p>输入的值: {{ inputValue }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const inputValue = ref('')
const handleCustomEvent = (data) => {
console.log('收到子组件的事件:', data)
alert(`子组件消息: ${data.message}`)
}
</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
49
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
四、实战案例:交互式任务列表
vue
<template>
<div class="todo-app">
<h2>任务列表</h2>
<!-- 添加任务 -->
<div class="add-todo">
<input
v-model="newTodo"
@keyup.enter="addTodo"
placeholder="输入任务并按回车添加"
class="todo-input"
/>
<button @click="addTodo" class="add-btn">添加</button>
</div>
<!-- 任务列表 -->
<ul class="todo-list">
<li
v-for="(todo, index) in todos"
:key="todo.id"
:class="{ completed: todo.completed }"
class="todo-item"
>
<span
@click="toggleTodo(index)"
class="todo-text"
>
{{ todo.text }}
</span>
<!-- 鼠标悬停显示操作按钮 -->
<div class="todo-actions">
<button
@click.stop="editTodo(index)"
class="edit-btn"
>
编辑
</button>
<button
@click.stop="deleteTodo(index)"
class="delete-btn"
>
删除
</button>
</div>
</li>
</ul>
<!-- 统计信息 -->
<div class="stats">
<p>总任务: {{ todos.length }}</p>
<p>已完成: {{ completedCount }}</p>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 任务数据
const newTodo = ref('')
const todos = ref([
{ id: 1, text: '学习Vue3事件处理', completed: false },
{ id: 2, text: '编写示例代码', completed: true }
])
// 计算属性:已完成任务数量
const completedCount = computed(() => {
return todos.value.filter(todo => todo.completed).length
})
// 添加新任务
const addTodo = () => {
if (newTodo.value.trim()) {
todos.value.push({
id: Date.now(),
text: newTodo.value.trim(),
completed: false
})
newTodo.value = '' // 清空输入框
}
}
// 切换任务完成状态
const toggleTodo = (index) => {
todos.value[index].completed = !todos.value[index].completed
}
// 编辑任务
const editTodo = (index) => {
const newText = prompt('编辑任务:', todos.value[index].text)
if (newText !== null) {
todos.value[index].text = newText.trim()
}
}
// 删除任务
const deleteTodo = (index) => {
if (confirm('确定要删除这个任务吗?')) {
todos.value.splice(index, 1)
}
}
</script>
<style scoped>
.todo-app {
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.todo-input {
padding: 8px;
margin-right: 10px;
width: 200px;
}
.add-btn {
padding: 8px 16px;
background: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
margin: 5px 0;
background: #f5f5f5;
border-radius: 4px;
}
.todo-item:hover .todo-actions {
opacity: 1;
}
.todo-text {
cursor: pointer;
flex-grow: 1;
}
.todo-text:hover {
color: #2196F3;
}
.completed .todo-text {
text-decoration: line-through;
color: #888;
}
.todo-actions {
opacity: 0;
transition: opacity 0.3s;
}
.edit-btn, .delete-btn {
margin-left: 5px;
padding: 4px 8px;
border: none;
cursor: pointer;
}
.edit-btn {
background: #FFC107;
}
.delete-btn {
background: #F44336;
color: white;
}
.stats {
margin-top: 20px;
padding: 10px;
background: #e3f2fd;
border-radius: 4px;
}
</style>
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
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
五、常见问题解答
Q1:为什么有时候需要使用 .value
?
A:在 script setup
语法中,ref
创建的响应式变量需要通过 .value
访问实际值,但在模板中会自动解包,不需要 .value
。
Q2:事件修饰符可以串联使用吗?
A:可以!例如 @click.stop.prevent
会同时阻止冒泡和默认行为。
Q3:如何监听自定义组件的事件?
A:使用 v-on
或 @
监听子组件通过 emit
发出的事件,如 <ChildComponent @custom-event="handler" />
。
Q4:原生事件和自定义事件有什么区别?
A:原生事件是浏览器自带的事件(click、input等),自定义事件是组件通过 emit
发出的事件。
总结
Vue3 的事件处理系统既强大又灵活,通过本文的学习,你应该掌握了:
- 基础事件绑定:
@事件名="处理方法"
- 事件修饰符:
.stop
、.prevent
、.once
等 - 按键处理:
.enter
、.esc
、.ctrl
等 - 高级技巧:动态事件名、多事件处理、事件传递
记住实践是学习的最好方式,多动手编写代码,逐步掌握这些概念。遇到问题时,回看本文的示例代码,相信你能快速找到解决方案!
VN/A |
本站访客数
--次 本站总访问量
--人次