Vue 事件绑定完全指南:官方文档未详述的事件大全

Vue 的 v-on(即 @)本质上是对原生 DOM 事件的直接透传,凡是浏览器原生支持的事件,Vue 均可绑定。本文系统整理官方文档未重点列出、但实际开发中极具价值的各类事件,并附带修饰符组合、编译器处理机制与架构层面的深度说明。


一、编译器如何处理事件名

理解 Vue 如何处理事件名,是掌握所有事件绑定的基础。

1.1 事件名转换规则

Vue 编译器(@vue/compiler-dom)在处理 @eventName 时执行以下转换:

ruby 复制代码
模板写法          →    编译产物(props key)
@click            →    onClick
@dblclick         →    onDblclick
@mouseenter       →    onMouseenter
@keydown.enter    →    onKeydown(运行时过滤 key)
@update:modelValue →   onUpdate:modelValue

核心转换函数来自 @vue/shared

javascript 复制代码
import { toHandlerKey, camelize } from '@vue/shared'

toHandlerKey('click')       // 'onClick'
toHandlerKey('dblclick')    // 'onDblclick'
toHandlerKey(camelize('my-event')) // 'onMyEvent'

1.2 事件名大小写规则

xml 复制代码
<!-- 以下三种写法在组件上等价(Vue 自动转换) -->
@myEvent="handler"
@my-event="handler"
@my_event="handler"

<!-- 原生 DOM 元素上:保持原始大小写,不做转换 -->
<div @dblclick="handler" />   <!-- ✅ 正确 -->
<div @DblClick="handler" />   <!-- ❌ 无法触发(原生事件名全小写) -->

二、鼠标事件(Mouse Events)

2.1 文档常见事件

xml 复制代码
@click        <!-- 单击 -->
@mouseover    <!-- 鼠标悬入(含子元素冒泡) -->
@mouseout     <!-- 鼠标离开(含子元素冒泡) -->
@mousemove    <!-- 鼠标移动 -->
@mousedown    <!-- 鼠标按下 -->
@mouseup      <!-- 鼠标抬起 -->
@contextmenu  <!-- 右键菜单 -->

2.2 文档未重点说明的鼠标事件

@dblclick --- 双击

ini 复制代码
<div @dblclick="onDblClick">双击我</div>
csharp 复制代码
function onDblClick(event: MouseEvent) {
  // event.detail === 2,表示这是第二次点击
  console.log('双击坐标:', event.clientX, event.clientY)
}

注意@click@dblclick 同时绑定时,双击会先触发两次 click 再触发一次 dblclick。如需互斥,需手动用定时器处理:

javascript 复制代码
let clickTimer: ReturnType<typeof setTimeout> | null = null

function handleClick() {
  if (clickTimer) return // 等待期间忽略单击
  clickTimer = setTimeout(() => {
    clickTimer = null
    doSingleClick()
  }, 250)
}

function handleDblClick() {
  if (clickTimer) {
    clearTimeout(clickTimer)
    clickTimer = null
  }
  doDblClick()
}

@mouseenter / @mouseleave --- 不冒泡的悬停事件

xml 复制代码
<!-- mouseover/mouseout 会因子元素冒泡频繁触发 -->
<!-- mouseenter/mouseleave 只在进入/离开元素本身时触发一次 -->
<div
  @mouseenter="isHovered = true"
  @mouseleave="isHovered = false"
  :class="{ hovered: isHovered }"
>
  悬停区域(推荐用这对,而非 mouseover/mouseout)
</div>

架构建议 :绝大多数悬停交互应使用 mouseenter/mouseleave 而非 mouseover/mouseout,后者在子元素多的情况下会产生大量无意义的事件触发。


@auxclick --- 非主键点击(中键/侧键)

xml 复制代码
<!-- 鼠标中键或其他非左键的点击 -->
<a @auxclick.prevent="openInNewTab(url)">中键点击新标签打开</a>
csharp 复制代码
function onAuxClick(event: MouseEvent) {
  if (event.button === 1) {
    // button 1 = 中键
    event.preventDefault() // 阻止中键默认的自动滚动
    window.open(url, '_blank')
  }
}

@clickbutton 属性区分左中右键

ini 复制代码
<div @mousedown="handleMouseDown">多键区域</div>
javascript 复制代码
function handleMouseDown(event: MouseEvent) {
  switch (event.button) {
    case 0: console.log('左键'); break
    case 1: console.log('中键'); break
    case 2: console.log('右键(通常触发 contextmenu)'); break
    case 3: console.log('侧键后退'); break
    case 4: console.log('侧键前进'); break
  }
}

@click 的鼠标按键修饰符

xml 复制代码
<!-- Vue 内置的鼠标按键修饰符(文档有但常被忽视) -->
<div @click.left="onLeft">仅左键触发</div>
<div @click.right="onRight">仅右键触发(不显示系统菜单)</div>
<div @click.middle="onMiddle">仅中键触发</div>

三、键盘事件(Keyboard Events)

3.1 基础事件

xml 复制代码
@keydown   <!-- 按键按下(持续触发) -->
@keyup     <!-- 按键抬起 -->
@keypress  <!-- ⚠️ 已废弃,不推荐使用 -->

3.2 按键修饰符完整列表

Vue 内置了以下按键别名,但文档中许多组合从未被举例

xml 复制代码
<!-- 字母数字类 -->
@keydown.enter="submit"
@keydown.tab="onTab"
@keydown.delete="onDelete"     <!-- 同时匹配 Delete 和 Backspace -->
@keydown.esc="onEsc"           <!-- 等价于 Escape -->
@keydown.space="onSpace"
@keydown.up="moveUp"
@keydown.down="moveDown"
@keydown.left="moveLeft"
@keydown.right="moveRight"

<!-- 功能键修饰符(可组合) -->
@keydown.ctrl.enter="submitForm"    <!-- Ctrl + Enter -->
@keydown.shift.enter="newline"      <!-- Shift + Enter -->
@keydown.alt.left="goBack"          <!-- Alt + ← -->
@keydown.meta.k="openCommandPalette" <!-- Cmd/Win + K -->
@keydown.ctrl.shift.z="redo"        <!-- Ctrl + Shift + Z -->

<!-- 直接使用 KeyboardEvent.key 值(kebab-case) -->
@keydown.page-up="scrollUp"
@keydown.page-down="scrollDown"
@keydown.home="gotoTop"
@keydown.end="gotoBottom"
@keydown.f1="showHelp"
@keydown.f11="toggleFullscreen"

<!-- 精确修饰符:只匹配完全一致的按键组合 -->
@keydown.exact.ctrl.a="selectAll"   <!-- 只有 Ctrl+A,有其他键不触发 -->

3.3 自定义按键别名(Vue 2 遗留,Vue 3 移除)

Vue 3 已移除 Vue.config.keyCodes,推荐直接使用 KeyboardEvent.key 的 kebab-case 形式:

ini 复制代码
<!-- Vue 3 直接使用原生 key 名 -->
@keydown.arrow-up="moveUp"
@keydown.home="gotoTop"
@keydown.escape="close"
@keydown.tab="focusNext"

3.4 IME 输入法相关事件(极重要但完全未文档化)

xml 复制代码
<!-- compositionstart: 开始输入法输入(中日韩输入时) -->
<!-- compositionupdate: 输入法候选字更新 -->
<!-- compositionend: 输入法输入完成,确认上屏 -->
<input
  @compositionstart="isComposing = true"
  @compositionupdate="onCompositionUpdate"
  @compositionend="isComposing = false; onInput($event)"
  @input="!isComposing && onInput($event)"
/>
csharp 复制代码
const isComposing = ref(false)

// 正确处理中文输入的搜索框
function onInput(event: Event) {
  if (isComposing.value) return // 输入法选字过程中不触发搜索
  const value = (event.target as HTMLInputElement).value
  doSearch(value)
}

这是处理中文/日文/韩文输入时最常见的坑,v-model 内部已处理此问题,但手动监听 @input 时必须自行处理。


四、表单与输入事件(Form & Input Events)

4.1 @input vs @change 的区别

xml 复制代码
<!-- @input:每次值变化立即触发(包括输入法过程中) -->
<input @input="onInput" />

<!-- @change:失去焦点且值有变化时触发(或按 Enter) -->
<input @change="onChange" />

<!-- select 元素 -->
<select @change="onSelect">
  <option>A</option>
</select>

<!-- checkbox/radio -->
<input type="checkbox" @change="onCheckboxChange" />

4.2 焦点事件

xml 复制代码
@focus     <!-- 获得焦点(不冒泡) -->
@blur      <!-- 失去焦点(不冒泡) -->
@focusin   <!-- 获得焦点(冒泡,可在父元素监听子元素焦点) -->
@focusout  <!-- 失去焦点(冒泡) -->
xml 复制代码
<!-- 实用场景:监听整个表单区域内的焦点状态 -->
<div
  @focusin="isFormActive = true"
  @focusout="isFormActive = false"
>
  <input placeholder="字段1" />
  <input placeholder="字段2" />
</div>

focusin/focusout 会冒泡,而 focus/blur 不会------这是在父容器层面感知子元素焦点的标准方式。

4.3 @reset / @submit

xml 复制代码
<!-- 原生 form 事件 -->
<form
  @submit.prevent="handleSubmit"
  @reset="handleReset"
>
  <input type="text" />
  <button type="submit">提交</button>
  <button type="reset">重置</button>
</form>

4.4 @invalid --- 原生表单验证失败

xml 复制代码
<!-- 原生 HTML5 表单验证失败时触发 -->
<input
  type="email"
  required
  @invalid.prevent="showCustomError($event)"
/>
csharp 复制代码
function showCustomError(event: Event) {
  const input = event.target as HTMLInputElement
  // 阻止浏览器默认气泡提示,使用自定义 UI
  input.setCustomValidity('请输入有效的邮箱地址')
}

4.5 @select --- 文本选中

xml 复制代码
<!-- 用户在 input/textarea 中选中文字时触发 -->
<textarea
  @select="onTextSelect"
/>
vbnet 复制代码
function onTextSelect(event: Event) {
  const textarea = event.target as HTMLTextAreaElement
  const selectedText = textarea.value.slice(
    textarea.selectionStart!,
    textarea.selectionEnd!
  )
  console.log('选中的文字:', selectedText)
}

五、拖拽事件(Drag & Drop Events)

这是文档中完全没有专门章节讲解的一类事件,但实现原生拖拽时必须用到。

xml 复制代码
<!-- 被拖拽的元素上 -->
<div
  draggable="true"
  @dragstart="onDragStart"    <!-- 开始拖拽 -->
  @drag="onDragging"          <!-- 拖拽中(持续触发) -->
  @dragend="onDragEnd"        <!-- 拖拽结束(无论是否成功) -->
>
  拖我
</div>

<!-- 放置目标上 -->
<div
  @dragenter="onDragEnter"    <!-- 拖拽元素进入目标区域 -->
  @dragover.prevent="onDragOver"  <!-- 必须 prevent 才能触发 drop! -->
  @dragleave="onDragLeave"    <!-- 拖拽元素离开目标区域 -->
  @drop.prevent="onDrop"      <!-- 放置 -->
>
  放这里
</div>
csharp 复制代码
function onDragStart(event: DragEvent) {
  event.dataTransfer!.setData('text/plain', 'payload-data')
  event.dataTransfer!.effectAllowed = 'move' // 'copy' | 'move' | 'link'
}

function onDrop(event: DragEvent) {
  const data = event.dataTransfer!.getData('text/plain')
  console.log('接收到:', data)
}

@dragover.prevent 是关键 :不调用 preventDefault() 的话,@drop 事件永远不会触发。

5.1 文件拖拽上传

ini 复制代码
<div
  @dragover.prevent
  @drop.prevent="onFileDrop"
  class="upload-zone"
>
  拖拽文件到此处上传
</div>
javascript 复制代码
function onFileDrop(event: DragEvent) {
  const files = Array.from(event.dataTransfer!.files)
  files.forEach(file => {
    console.log('文件名:', file.name, '大小:', file.size)
    uploadFile(file)
  })
}

六、剪贴板事件(Clipboard Events)

ini 复制代码
<input
  @cut="onCut"
  @copy="onCopy"
  @paste="onPaste"
/>
javascript 复制代码
function onCopy(event: ClipboardEvent) {
  // 修改复制的内容
  event.preventDefault()
  const selection = window.getSelection()?.toString() ?? ''
  event.clipboardData!.setData('text/plain', `${selection} ------ 来自我的网站`)
}

function onPaste(event: ClipboardEvent) {
  event.preventDefault()
  const text = event.clipboardData!.getData('text/plain')
  // 过滤内容后再插入
  const sanitized = sanitizeHtml(text)
  document.execCommand('insertText', false, sanitized)
}

// 读取粘贴的文件(如截图)
function onPasteWithFile(event: ClipboardEvent) {
  const items = Array.from(event.clipboardData!.items)
  const imageItem = items.find(item => item.type.startsWith('image/'))
  if (imageItem) {
    const file = imageItem.getAsFile()!
    uploadImage(file)
  }
}

七、滚动与视口事件

7.1 @scroll

xml 复制代码
<!-- 元素滚动(注意:不冒泡,需绑在实际滚动的元素上) -->
<div class="scroll-container" @scroll="onScroll">
  <div class="content">长内容...</div>
</div>
csharp 复制代码
function onScroll(event: Event) {
  const el = event.target as HTMLElement
  const { scrollTop, scrollHeight, clientHeight } = el
  // 判断是否滚动到底部
  if (scrollTop + clientHeight >= scrollHeight - 10) {
    loadMore()
  }
}

7.2 @wheel --- 鼠标滚轮

xml 复制代码
<!-- wheel 比 scroll 更底层,可以阻止滚动 -->
<div @wheel.prevent="onWheel">阻止此区域的页面滚动</div>
csharp 复制代码
function onWheel(event: WheelEvent) {
  event.deltaY  // 垂直滚动量(正值向下,负值向上)
  event.deltaX  // 水平滚动量
  event.deltaMode // 0=像素, 1=行, 2=页
}

@scroll 无法 preventDefault()@wheel 可以------两者用途不同。


八、触摸事件(Touch Events)

xml 复制代码
<div
  @touchstart="onTouchStart"   <!-- 手指接触屏幕 -->
  @touchmove.prevent="onTouchMove"  <!-- 手指移动(prevent 阻止页面滚动) -->
  @touchend="onTouchEnd"       <!-- 手指抬起 -->
  @touchcancel="onTouchCancel" <!-- 触摸被中断(来电等) -->
>
  触摸区域
</div>
csharp 复制代码
let startX = 0
let startY = 0

function onTouchStart(event: TouchEvent) {
  startX = event.touches[0].clientX
  startY = event.touches[0].clientY
}

function onTouchEnd(event: TouchEvent) {
  const endX = event.changedTouches[0].clientX
  const endY = event.changedTouches[0].clientY
  const dx = endX - startX
  const dy = endY - startY

  // 简单滑动手势识别
  if (Math.abs(dx) > Math.abs(dy)) {
    dx > 0 ? emit('swipe-right') : emit('swipe-left')
  } else {
    dy > 0 ? emit('swipe-down') : emit('swipe-up')
  }
}

8.1 指针事件(Pointer Events)--- 统一鼠标+触摸+笔

ini 复制代码
<!-- PointerEvent 是鼠标事件 + 触摸事件的统一抽象(推荐使用) -->
<div
  @pointerdown="onPointerDown"
  @pointermove="onPointerMove"
  @pointerup="onPointerUp"
  @pointerenter="onPointerEnter"
  @pointerleave="onPointerLeave"
  @pointercancel="onPointerCancel"
  @gotpointercapture="onGotCapture"
  @lostpointercapture="onLostCapture"
>
</div>
csharp 复制代码
function onPointerDown(event: PointerEvent) {
  event.pointerId    // 指针 ID(多点触控时区分不同手指)
  event.pointerType  // 'mouse' | 'pen' | 'touch'
  event.pressure     // 压力值 0-1(笔/触控板支持)
  event.tiltX        // 笔的倾斜角
  event.isPrimary    // 是否是主指针(多点触控时第一个接触的手指)
  
  // 捕获指针:即使鼠标移出元素也继续接收事件
  ;(event.target as HTMLElement).setPointerCapture(event.pointerId)
}

架构建议:新项目的拖拽、绘图、手势等功能优先使用 Pointer Events,而非分别处理 Mouse Events 和 Touch Events。**


九、媒体事件(Media Events)

这是文档完全没有覆盖的一类,对音视频开发至关重要。

xml 复制代码
<video
  ref="videoRef"
  @loadstart="onLoadStart"       <!-- 开始加载 -->
  @loadedmetadata="onMetaLoaded" <!-- 元数据加载完成(可获取时长、尺寸) -->
  @loadeddata="onDataLoaded"     <!-- 当前帧数据加载完成 -->
  @canplay="onCanPlay"           <!-- 可以开始播放 -->
  @canplaythrough="onCanPlayThrough" <!-- 预计不会缓冲中断地播放到结束 -->
  @play="onPlay"                 <!-- 开始播放 -->
  @playing="onPlaying"           <!-- 在缓冲后恢复播放 -->
  @pause="onPause"               <!-- 暂停 -->
  @ended="onEnded"               <!-- 播放结束 -->
  @timeupdate="onTimeUpdate"     <!-- 播放进度更新(约每秒 4-66 次) -->
  @seeking="onSeeking"           <!-- 用户拖拽进度条中 -->
  @seeked="onSeeked"             <!-- 拖拽完成 -->
  @waiting="onWaiting"           <!-- 缓冲中,等待数据 -->
  @stalled="onStalled"           <!-- 浏览器尝试获取数据但暂停 -->
  @error="onError"               <!-- 加载/播放错误 -->
  @volumechange="onVolumeChange" <!-- 音量或静音状态变化 -->
  @ratechange="onRateChange"     <!-- 播放速率变化 -->
  @durationchange="onDurChange"  <!-- 时长变化 -->
  @progress="onProgress"         <!-- 下载进度更新 -->
  @suspend="onSuspend"           <!-- 浏览器停止加载(数据已足够) -->
  @abort="onAbort"               <!-- 资源加载中止(非错误) -->
  @emptied="onEmptied"           <!-- 媒体资源被清空 -->
>
</video>
vbnet 复制代码
function onTimeUpdate(event: Event) {
  const video = event.target as HTMLVideoElement
  const progress = (video.currentTime / video.duration) * 100
  progressBar.value = progress
}

function onMetaLoaded(event: Event) {
  const video = event.target as HTMLVideoElement
  console.log('视频时长:', video.duration)
  console.log('视频尺寸:', video.videoWidth, 'x', video.videoHeight)
}

十、窗口与文档事件(配合 useEventListener

这类事件不能直接在 Vue 模板中用 @ 绑定 ,需要在 onMounted 中手动注册,或使用 VueUse 的 useEventListener

javascript 复制代码
import { onMounted, onUnmounted } from 'vue'

// 方式一:手动管理
onMounted(() => {
  window.addEventListener('resize', onResize)
  window.addEventListener('online', onOnline)
  window.addEventListener('offline', onOffline)
  document.addEventListener('visibilitychange', onVisibilityChange)
})
onUnmounted(() => {
  window.removeEventListener('resize', onResize)
  // ...
})

// 方式二:VueUse(推荐)
import { useEventListener } from '@vueuse/core'
useEventListener(window, 'resize', onResize)
useEventListener(document, 'visibilitychange', onVisibilityChange)

常见窗口级事件清单

javascript 复制代码
// 视口
window.addEventListener('resize', handler)          // 窗口大小变化
window.addEventListener('scroll', handler)          // 窗口滚动
window.addEventListener('orientationchange', handler) // 屏幕旋转

// 网络状态
window.addEventListener('online', handler)          // 网络恢复
window.addEventListener('offline', handler)         // 网络断开

// 页面生命周期
window.addEventListener('load', handler)            // 页面全部资源加载完毕
window.addEventListener('beforeunload', handler)    // 页面即将卸载(可弹确认框)
window.addEventListener('unload', handler)          // 页面卸载中
window.addEventListener('pagehide', handler)        // 页面隐藏(bfcache 友好)
window.addEventListener('pageshow', handler)        // 页面显示

// 历史记录
window.addEventListener('popstate', handler)        // 浏览器前进/后退
window.addEventListener('hashchange', handler)      // hash 变化

// 焦点
window.addEventListener('focus', handler)           // 窗口获得焦点
window.addEventListener('blur', handler)            // 窗口失去焦点

// 文档可见性
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    pauseVideo()  // 切换标签页时暂停视频
  }
})

// 存储
window.addEventListener('storage', (event: StorageEvent) => {
  // 跨标签页的 localStorage 变化通知
  console.log(event.key, event.oldValue, event.newValue)
})

// 全屏
document.addEventListener('fullscreenchange', handler)

// 打印
window.addEventListener('beforeprint', handler)
window.addEventListener('afterprint', handler)

// 游戏手柄
window.addEventListener('gamepadconnected', handler)
window.addEventListener('gamepaddisconnected', handler)

十一、Intersection Observer 事件(元素进入视口)

这不是传统 DOM 事件,但在 Vue 中有标准的封装方式,常用于懒加载:

typescript 复制代码
// 自定义指令实现 v-intersect
const vIntersect = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            binding.value(entry)
            if (binding.modifiers.once) {
              observer.unobserve(el)
            }
          }
        })
      },
      {
        threshold: binding.arg ? parseFloat(binding.arg as string) : 0,
        rootMargin: '0px 0px -50px 0px'
      }
    )
    observer.observe(el)
    ;(el as any).__intersectObserver__ = observer
  },
  unmounted(el: HTMLElement) {
    ;(el as any).__intersectObserver__?.disconnect()
  }
}

// 使用
app.directive('intersect', vIntersect)
ini 复制代码
<img
  v-intersect.once="onVisible"
  :src="placeholder"
  data-src="real-image.jpg"
/>

十二、自定义组件事件

12.1 defineEmits 完整用法

typescript 复制代码
// 带类型验证(运行时 + 编译时双重验证)
const emit = defineEmits<{
  change: [value: string]                      // 单参数
  update: [id: number, value: string]          // 多参数
  'node:select': [node: TreeNode]              // 带命名空间的事件名
  submit: [data: FormData, reset: () => void]  // 包含回调函数参数
}>()

// 或带运行时验证的对象语法
const emit = defineEmits({
  change: (value: string) => {
    // 返回 false 表示验证失败,控制台输出警告
    return typeof value === 'string' && value.length > 0
  },
  submit: null // null 表示不验证
})

12.2 v-model 多绑定(Vue 3.4+)

xml 复制代码
<!-- 父组件 -->
<MyForm
  v-model:title="title"
  v-model:content="content"
  v-model:tags="tags"
/>
c 复制代码
// 子组件
const props = defineProps<{
  title: string
  content: string
  tags: string[]
}>()

const emit = defineEmits<{
  'update:title': [value: string]
  'update:content': [value: string]
  'update:tags': [value: string[]]
}>()

12.3 v-model 修饰符透传

xml 复制代码
<!-- 父 -->
<MyInput v-model.trim.uppercase="text" />
csharp 复制代码
// 子组件处理修饰符
const props = defineProps<{
  modelValue: string
  modelModifiers?: {
    trim?: boolean
    uppercase?: boolean
  }
}>()

const emit = defineEmits<{
  'update:modelValue': [value: string]
}>()

function onInput(event: Event) {
  let value = (event.target as HTMLInputElement).value
  if (props.modelModifiers?.trim) value = value.trim()
  if (props.modelModifiers?.uppercase) value = value.toUpperCase()
  emit('update:modelValue', value)
}

十三、修饰符组合速查表

perl 复制代码
事件修饰符(可链式组合):
.stop       → event.stopPropagation()
.prevent    → event.preventDefault()
.self       → 只有 event.target === 当前元素时触发
.capture    → 使用捕获模式(从上到下,而非冒泡)
.once       → 只触发一次,之后自动移除监听
.passive    → 告知浏览器不调用 preventDefault(scroll 性能优化)
.native     → ⚠️ Vue 3 已移除,请用 v-bind="$attrs" 透传
.exact      → 精确匹配按键组合,不允许其他修饰键

实用组合:
@click.stop.prevent      → 阻止冒泡 + 阻止默认行为
@scroll.passive          → 高性能滚动监听(不能与 .prevent 同用)
@keydown.ctrl.exact.s    → 精确的 Ctrl+S(有其他键不触发)
@click.once              → 按钮防重复点击(仅首次有效)
@touchmove.prevent       → 阻止移动端页面随手势滚动

十四、在 Render Function 中绑定事件

javascript 复制代码
import { h, withModifiers } from 'vue'

// withModifiers:在 render function 中应用事件修饰符
h('button', {
  onClick: withModifiers(
    (event: MouseEvent) => { console.log('clicked') },
    ['stop', 'prevent']  // 等价于 @click.stop.prevent
  )
})

// 多事件监听
h('input', {
  onInput: handler1,
  onChange: handler2,
  'onUpdate:modelValue': (val) => (modelValue.value = val)
})

// 动态事件名
const eventName = 'onClick'
h('div', { [eventName]: handler })

总结

Vue 的事件系统本质上是对原生 DOM 事件的完整代理。所有浏览器原生事件均可通过 @eventName 直接绑定,编译器负责将其转换为 onEventName 形式的 prop,运行时通过 addEventListener 实际注册。

几个关键认知

  • mouseenter/mouseleave 优于 mouseover/mouseout(不冒泡,性能更好)
  • focusin/focusout 是父元素感知子元素焦点的正确方式
  • 中文输入必须处理 compositionstart/end,否则 @input 会在选字中间触发
  • @dragover.prevent@drop 能触发的前提条件
  • Pointer Events 是未来趋势,统一了鼠标、触控、笔的处理
  • @scroll.passive 是长列表滚动性能的关键修饰符
相关推荐
天涯学馆2 小时前
从 V8 引擎看 JS 代码是如何一步步变成机器指令的
前端·javascript·面试
new code Boy2 小时前
JavaScript转Python”的速查表
开发语言·javascript·python
Elaine3362 小时前
【通过 Vue 实例劫持突破 Web 编辑器的粘贴限制】
前端·javascript·vue.js·chrome devtools·前端逆向
哔哩哔哩技术2 小时前
从“截图大法”到真实交互:B站专栏视频卡的技术革命
前端
zhensherlock2 小时前
Protocol Launcher 系列:一键唤起 Windsurf 智能 IDE
javascript·ide·vscode·ai·typescript·github·ai编程
十步杀一人_千里不留行2 小时前
TypeScript 里的 Type Guard 是什么
javascript·ubuntu·typescript
程序员讲BPM工作流2 小时前
npm非全局方式安装小龙虾OpenClaw
前端·npm·node.js
阿成学长_Cain2 小时前
Linux alias 命令详解:从入门到高级用法
linux·前端·chrome
进击切图仔2 小时前
生成 .so 和使用 .so
java·javascript·算法