Vue 事件机制全面解析:原生事件、自定义事件与 DOM 冒泡完全讲透
为什么要理解 Vue 事件?
-
只知道 @click,却分不清它属于谁
-
子组件不 emit,父组件 @click 为何能触发?
-
面试中被问 "emit 和 @click 区别?
1.Vue事件的核心机制
1.1 原生事件(native events)
当 @drop 写在 HTML 原生标签上,例如:
ini
<div @drop="handleDrop"></div>
这说明:
- 监听的是浏览器 DOM 的 drop 事件
- 用户把文件拖到 上时触发
- 不需要任何子组件 emit
示例如下:
html
<div
class="chunk-upload-trigger"
@drop="handleDrop"
@dragenter="handleDragEnter"
@dragleave="handleDragLeave"
>
这些都是原生 DOM 事件,没有任何"子组件触发"的概念
1.2 子组件自定义事件(子组件 emit)
父组件中写入:
html
<Child @file-selected="onFileSelected" />
等待子组件 Child 调用
javascript
emit('file-selected', file)
父组件收到这个事件并执行 onFileSelected
- ChunkUploadTrigger 内部 emit('file-selected', file)
- UploadPanel 接收到
- 执行 handleFileSelected
这种完全不涉及 DOM 事件。
1.3 浏览器 DOM 的事件冒泡机制
父组件中
ini
<ChunkUploadTrigger @drop="handleDrop" />
子组件中
ini
<div @drop="handleDropInside"></div>
子组件不用 emit 通知父组件,子组件父组件都是同名的事件,此时父组件的事件就会穿透绑定到子组件根元素,两者的执行顺序,childDrop() 会触发,parentDrop() 也会触发,两者触发顺序按 DOM 事件流来(冒泡顺序)
DOM 事件冒泡不关心你是不是在一个文件、一个组件,它只关心 DOM 节点树。
markdown
父组件根 DOM
└── 子组件根 DOM
└── 子组件内部 DOM
所以虽然写在了两个.vue 文件,但是渲染出来的其实是跟在一个文件里写了两个 父子div 没有任何区别,事件冒泡就是沿着这棵 DOM 树往上走的
xml
<Parent>
<ChunkUploadTrigger />
</Parent>
<div class="parent-root">
<div class="chunk-upload-trigger"> ← 子组件根节点
<div>...</div>
</div>
</div>
2.事件相关的实用补充
2.4 DOM 常用事件
浏览器原生事件,放在 HTML 标签上就能触发
鼠标事件
click,dblclick,mousedown,mouseup
键盘事件
keydown,keyup,keypress
输入 & 表单事件
dragenter,dragover,dragleave,drop
其他 DOM 事件
scroll,wheel,resize,load,error
2.5 Vue 事件修饰符总览
| 修饰符 | 含义 | 对应的 DOM 行为 |
|---|---|---|
| .stop | 阻止事件冒泡 | event.stopPropagation() |
| .prevent | 阻止默认行为 | event.preventDefault() |
| .capture | 使用捕获模式 | addEventListener(..., true) |
| .self | 只有事件目标是当前元素时触发 | event.target === currentTarget |
| .once | 事件只触发一次 | 自动 removeEventListener |
| .passive | 表示监听器不会调用 preventDefault | passive: true |
比如:
html
@dragover.prevent="handleDragOver"