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')
  }
}

@click 的 button 属性区分左中右键

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 是长列表滚动性能的关键修饰符
相关推荐
Csvn3 分钟前
性能监控
前端
Highcharts.js5 分钟前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn29 分钟前
构建工具
前端
FRing031 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷2 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen3 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒3 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫4 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
jerrywus4 小时前
我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上
前端·agent·claude
孟陬6 小时前
Foobar 技术周刊#159:当看不懂代码时,如何高效排障调试。脏页陷阱:一条只读查询,写入数 TB 数据。推测解码:大模型加速利器
前端·人工智能·后端