Vue3 事件处理 v-on 指令 (@) 详解

Vue3 事件处理 v-on 指令 (@) 详解

核心概念理解

什么是事件处理?

事件处理就是响应用户的操作,比如点击按钮、输入文字、鼠标悬停等。Vue 通过 v-on 指令(简写为 @)来监听和处理这些事件。

基础用法

1. 简单事件处理

vue 复制代码
<template>
  <div class="event-demo">
    <h2>基础事件处理</h2>
    
    <!-- 点击事件 -->
    <button @click="handleClick">点击我</button>
    
    <!-- 双击事件 -->
    <button @dblclick="handleDoubleClick">双击我</button>
    
    <!-- 鼠标悬停事件 -->
    <div 
      @mouseenter="handleMouseEnter" 
      @mouseleave="handleMouseLeave"
      class="hover-box"
    >
      鼠标悬停区域
    </div>
    
    <!-- 输入事件 -->
    <input 
      @input="handleInput" 
      placeholder="输入文字试试"
      class="input-field"
    >
    <p>输入内容: {{ inputValue }}</p>
    
    <!-- 键盘事件 -->
    <input 
      @keyup.enter="handleEnter"
      @keyup.esc="handleEscape"
      placeholder="按 Enter 或 Esc"
      class="input-field"
    >
  </div>
</template>

<script setup>
import { ref } from 'vue'

const inputValue = ref('')

const handleClick = () => {
  alert('按钮被点击了!')
}

const handleDoubleClick = () => {
  alert('按钮被双击了!')
}

const handleMouseEnter = () => {
  console.log('鼠标进入')
}

const handleMouseLeave = () => {
  console.log('鼠标离开')
}

const handleInput = (event) => {
  inputValue.value = event.target.value
}

const handleEnter = () => {
  alert('按下了 Enter 键')
}

const handleEscape = () => {
  alert('按下了 Esc 键')
}
</script>

<style>
.event-demo {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.hover-box {
  width: 200px;
  height: 100px;
  background-color: #e3f2fd;
  border: 2px solid #2196f3;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 20px 0;
  cursor: pointer;
  transition: all 0.3s ease;
}

.hover-box:hover {
  background-color: #2196f3;
  color: white;
}

.input-field {
  width: 100%;
  padding: 10px;
  margin: 10px 0;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 16px;
}

button {
  padding: 10px 20px;
  margin: 5px;
  border: none;
  border-radius: 4px;
  background-color: #007bff;
  color: white;
  cursor: pointer;
  font-size: 16px;
  transition: background-color 0.2s ease;
}

button:hover {
  background-color: #0056b3;
}
</style>

事件修饰符

1. 常用事件修饰符

vue 复制代码
<template>
  <div class="modifier-demo">
    <h2>事件修饰符</h2>
    
    <!-- .prevent 阻止默认行为 -->
    <form @submit.prevent="handleSubmit">
      <input v-model="form.name" placeholder="姓名" required>
      <button type="submit">提交表单</button>
    </form>
    
    <!-- .stop 阻止事件冒泡 -->
    <div class="parent" @click="parentClick">
      父元素
      <div class="child" @click.stop="childClick">
        子元素 (阻止冒泡)
      </div>
    </div>
    
    <!-- .self 只在事件目标是元素自身时触发 -->
    <div class="self-demo" @click.self="selfClick">
      外层区域 (只点击这里才触发)
      <div class="inner">
        内层区域 (点击这里不触发)
      </div>
    </div>
    
    <!-- .once 只触发一次 -->
    <button @click.once="onceClick">
      只能点击一次的按钮
    </button>
    
    <!-- .capture 事件捕获模式 -->
    <div class="capture-demo" @click.capture="captureClick">
      捕获阶段
      <div class="capture-child" @click="childClick2">
        目标阶段
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const form = ref({
  name: ''
})

const handleSubmit = () => {
  alert(`提交成功: ${form.value.name}`)
  form.value.name = ''
}

const parentClick = () => {
  console.log('父元素被点击')
}

const childClick = () => {
  console.log('子元素被点击 (不会冒泡到父元素)')
}

const selfClick = () => {
  alert('只在点击外层区域时触发')
}

const onceClick = () => {
  alert('这个按钮只能点击一次')
}

const captureClick = () => {
  console.log('捕获阶段: 外层被点击')
}

const childClick2 = () => {
  console.log('目标阶段: 内层被点击')
}
</script>

<style>
.modifier-demo {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

form {
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
  margin-bottom: 20px;
}

form input {
  width: 100%;
  padding: 10px;
  margin-bottom: 10px;
  border: 1px solid #ced4da;
  border-radius: 4px;
}

.parent {
  width: 300px;
  height: 150px;
  background-color: #d1ecf1;
  border: 2px solid #bee5eb;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 20px 0;
  position: relative;
}

.child {
  width: 150px;
  height: 75px;
  background-color: #f8d7da;
  border: 2px solid #f5c6cb;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.self-demo {
  width: 300px;
  height: 150px;
  background-color: #d4edda;
  border: 2px solid #c3e6cb;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 20px 0;
  position: relative;
}

.inner {
  width: 150px;
  height: 75px;
  background-color: #fff3cd;
  border: 2px solid #ffeaa7;
  display: flex;
  align-items: center;
  justify-content: center;
}

.capture-demo {
  width: 300px;
  height: 150px;
  background-color: #e2e3e5;
  border: 2px solid #d6d8db;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 20px 0;
}

.capture-child {
  width: 150px;
  height: 75px;
  background-color: #cce7ff;
  border: 2px solid #b8daff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
</style>

2. 按键修饰符

vue 复制代码
<template>
  <div class="key-modifier-demo">
    <h2>按键修饰符</h2>
    
    <!-- 系统按键修饰符 -->
    <div class="key-section">
      <h3>系统按键</h3>
      <input 
        @keyup.ctrl.enter="handleCtrlEnter"
        placeholder="按 Ctrl + Enter"
        class="input-field"
      >
      <input 
        @keyup.alt.c="handleAltC"
        placeholder="按 Alt + C"
        class="input-field"
      >
      <input 
        @keyup.shift.space="handleShiftSpace"
        placeholder="按 Shift + 空格"
        class="input-field"
      >
    </div>
    
    <!-- 精确按键修饰符 -->
    <div class="key-section">
      <h3>精确按键</h3>
      <input 
        @keyup.exact="handleExact"
        placeholder="只按一个键"
        class="input-field"
      >
      <input 
        @keyup.ctrl.exact="handleCtrlExact"
        placeholder="只按 Ctrl 键"
        class="input-field"
      >
    </div>
    
    <!-- 常用按键别名 -->
    <div class="key-section">
      <h3>常用按键</h3>
      <input 
        @keyup.enter="handleEnter"
        placeholder="Enter 键"
        class="input-field"
      >
      <input 
        @keyup.tab="handleTab"
        placeholder="Tab 键"
        class="input-field"
      >
      <input 
        @keyup.delete="handleDelete"
        placeholder="Delete 键"
        class="input-field"
      >
      <input 
        @keyup.esc="handleEscape"
        placeholder="Esc 键"
        class="input-field"
      >
      <input 
        @keyup.space="handleSpace"
        placeholder="空格键"
        class="input-field"
      >
      <input 
        @keyup.up="handleUp"
        placeholder="上箭头"
        class="input-field"
      >
      <input 
        @keyup.down="handleDown"
        placeholder="下箭头"
        class="input-field"
      >
    </div>
  </div>
</template>

<script setup>
const handleCtrlEnter = () => {
  alert('按下了 Ctrl + Enter')
}

const handleAltC = () => {
  alert('按下了 Alt + C')
}

const handleShiftSpace = () => {
  alert('按下了 Shift + 空格')
}

const handleExact = () => {
  alert('只按了一个键')
}

const handleCtrlExact = () => {
  alert('只按了 Ctrl 键')
}

const handleEnter = () => {
  console.log('Enter 键被按下')
}

const handleTab = () => {
  console.log('Tab 键被按下')
}

const handleDelete = () => {
  console.log('Delete 键被按下')
}

const handleEscape = () => {
  console.log('Esc 键被按下')
}

const handleSpace = () => {
  console.log('空格键被按下')
}

const handleUp = () => {
  console.log('上箭头被按下')
}

const handleDown = () => {
  console.log('下箭头被按下')
}
</script>

<style>
.key-modifier-demo {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.key-section {
  margin-bottom: 30px;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
}

.key-section h3 {
  margin-top: 0;
  color: #495057;
}

.input-field {
  width: 100%;
  padding: 12px;
  margin: 10px 0;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 16px;
  outline: none;
  transition: border-color 0.2s ease;
}

.input-field:focus {
  border-color: #007bff;
  box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
</style>

事件参数传递

1. 内联处理器中的参数

vue 复制代码
<template>
  <div class="param-demo">
    <h2>事件参数传递</h2>
    
    <!-- 传递静态参数 -->
    <div class="button-group">
      <button @click="greet('Alice')">问候 Alice</button>
      <button @click="greet('Bob')">问候 Bob</button>
      <button @click="greet('Charlie')">问候 Charlie</button>
    </div>
    
    <!-- 传递动态参数 -->
    <div class="counter-demo">
      <h3>计数器示例</h3>
      <div v-for="counter in counters" :key="counter.id">
        <span>{{ counter.name }}: {{ counter.value }}</span>
        <button @click="increment(counter, 1)">+1</button>
        <button @click="increment(counter, 5)">+5</button>
        <button @click="increment(counter, -1)">-1</button>
        <button @click="resetCounter(counter)">重置</button>
      </div>
    </div>
    
    <!-- 传递原生事件对象 -->
    <div class="event-object-demo">
      <h3>事件对象</h3>
      <input 
        @input="handleInputEvent"
        placeholder="输入文字查看事件对象"
        class="input-field"
      >
      <p>事件类型: {{ eventInfo.type }}</p>
      <p>输入值: {{ eventInfo.value }}</p>
      <p>按键: {{ eventInfo.key }}</p>
    </div>
    
    <!-- 方法处理器 -->
    <div class="method-handler-demo">
      <h3>方法处理器</h3>
      <button @click="handleButtonClick">点击获取事件对象</button>
      <p>按钮坐标: {{ buttonPosition.x }}, {{ buttonPosition.y }}</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const counters = ref([
  { id: 1, name: '计数器1', value: 0 },
  { id: 2, name: '计数器2', value: 0 },
  { id: 3, name: '计数器3', value: 0 }
])

const eventInfo = ref({
  type: '',
  value: '',
  key: ''
})

const buttonPosition = ref({
  x: 0,
  y: 0
})

const greet = (name) => {
  alert(`你好, ${name}!`)
}

const increment = (counter, amount) => {
  counter.value += amount
}

const resetCounter = (counter) => {
  counter.value = 0
}

const handleInputEvent = (event) => {
  eventInfo.value = {
    type: event.type,
    value: event.target.value,
    key: event.key
  }
}

const handleButtonClick = (event) => {
  buttonPosition.value = {
    x: event.clientX,
    y: event.clientY
  }
  console.log('事件对象:', event)
}
</script>

<style>
.param-demo {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.button-group {
  margin-bottom: 30px;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
  text-align: center;
}

.button-group button {
  padding: 10px 15px;
  margin: 5px;
  border: none;
  border-radius: 4px;
  background-color: #28a745;
  color: white;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.button-group button:hover {
  background-color: #218838;
}

.counter-demo {
  margin-bottom: 30px;
  padding: 20px;
  background-color: #fff;
  border: 1px solid #dee2e6;
  border-radius: 8px;
}

.counter-demo div {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 0;
  padding: 10px;
  background-color: #f8f9fa;
  border-radius: 4px;
}

.counter-demo button {
  padding: 5px 10px;
  border: none;
  border-radius: 3px;
  background-color: #007bff;
  color: white;
  cursor: pointer;
  font-size: 12px;
}

.counter-demo button:hover {
  background-color: #0056b3;
}

.event-object-demo {
  margin-bottom: 30px;
  padding: 20px;
  background-color: #e3f2fd;
  border-radius: 8px;
}

.method-handler-demo {
  padding: 20px;
  background-color: #fff3cd;
  border-radius: 8px;
}

.input-field {
  width: 100%;
  padding: 10px;
  margin: 10px 0;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 16px;
}
</style>

实际应用示例

1. 表单处理

vue 复制代码
<template>
  <div class="form-demo">
    <h2>表单事件处理</h2>
    
    <form @submit.prevent="handleSubmit" class="user-form">
      <div class="form-group">
        <label>用户名:</label>
        <input 
          v-model="form.username"
          @blur="validateUsername"
          :class="{ 'error': errors.username }"
          required
        >
        <span v-if="errors.username" class="error-message">
          {{ errors.username }}
        </span>
      </div>
      
      <div class="form-group">
        <label>邮箱:</label>
        <input 
          v-model="form.email"
          @input="validateEmail"
          :class="{ 'error': errors.email }"
          type="email"
          required
        >
        <span v-if="errors.email" class="error-message">
          {{ errors.email }}
        </span>
      </div>
      
      <div class="form-group">
        <label>密码:</label>
        <input 
          v-model="form.password"
          @keyup.enter="handleSubmit"
          :class="{ 'error': errors.password }"
          type="password"
          required
        >
        <span v-if="errors.password" class="error-message">
          {{ errors.password }}
        </span>
      </div>
      
      <div class="form-group">
        <label>
          <input 
            v-model="form.agree"
            type="checkbox"
          >
          我同意用户协议
        </label>
      </div>
      
      <div class="form-actions">
        <button 
          type="submit" 
          :disabled="!isFormValid"
          @click.ctrl.exact="submitWithCtrl"
        >
          提交 (Ctrl+点击快速提交)
        </button>
        <button @click="resetForm" type="button">重置</button>
      </div>
    </form>
    
    <div v-if="submitted" class="success-message">
      <h3>提交成功!</h3>
      <p>用户名: {{ submittedData.username }}</p>
      <p>邮箱: {{ submittedData.email }}</p>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const form = ref({
  username: '',
  email: '',
  password: '',
  agree: false
})

const errors = ref({
  username: '',
  email: '',
  password: ''
})

const submitted = ref(false)
const submittedData = ref({})

// 表单验证
const validateUsername = () => {
  if (form.value.username.length < 3) {
    errors.value.username = '用户名至少3个字符'
  } else {
    errors.value.username = ''
  }
}

const validateEmail = () => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  if (!emailRegex.test(form.value.email)) {
    errors.value.email = '请输入有效的邮箱地址'
  } else {
    errors.value.email = ''
  }
}

const validatePassword = () => {
  if (form.value.password.length < 6) {
    errors.value.password = '密码至少6个字符'
  } else {
    errors.value.password = ''
  }
}

// 计算属性:表单是否有效
const isFormValid = computed(() => {
  return (
    form.value.username.length >= 3 &&
    /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.value.email) &&
    form.value.password.length >= 6 &&
    form.value.agree
  )
})

// 表单提交
const handleSubmit = () => {
  validateUsername()
  validateEmail()
  validatePassword()
  
  if (isFormValid.value) {
    submitted.value = true
    submittedData.value = { ...form.value }
    console.log('表单提交:', form.value)
  } else {
    alert('请检查表单信息')
  }
}

// Ctrl+点击提交
const submitWithCtrl = (event) => {
  event.preventDefault()
  console.log('Ctrl+点击提交')
  handleSubmit()
}

// 重置表单
const resetForm = () => {
  form.value = {
    username: '',
    email: '',
    password: '',
    agree: false
  }
  errors.value = {
    username: '',
    email: '',
    password: ''
  }
  submitted.value = false
}
</script>

<style>
.form-demo {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}

.user-form {
  background-color: #fff;
  padding: 30px;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

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

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

.form-group input[type="text"],
.form-group input[type="email"],
.form-group input[type="password"] {
  width: 100%;
  padding: 10px;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 16px;
  transition: border-color 0.2s ease;
}

.form-group input.error {
  border-color: #dc3545;
}

.form-group input:focus {
  outline: none;
  border-color: #007bff;
  box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.error-message {
  color: #dc3545;
  font-size: 14px;
  margin-top: 5px;
  display: block;
}

.form-group input[type="checkbox"] {
  margin-right: 8px;
}

.form-actions {
  display: flex;
  gap: 10px;
  margin-top: 30px;
}

.form-actions button {
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.form-actions button[type="submit"] {
  background-color: #28a745;
  color: white;
  flex: 1;
}

.form-actions button[type="submit"]:hover:not(:disabled) {
  background-color: #218838;
}

.form-actions button[type="submit"]:disabled {
  background-color: #6c757d;
  cursor: not-allowed;
}

.form-actions button[type="button"] {
  background-color: #6c757d;
  color: white;
}

.form-actions button[type="button"]:hover {
  background-color: #5a6268;
}

.success-message {
  margin-top: 20px;
  padding: 20px;
  background-color: #d4edda;
  border: 1px solid #c3e6cb;
  border-radius: 8px;
  text-align: center;
}
</style>

2. 鼠标和触摸事件

vue 复制代码
<template>
  <div class="mouse-demo">
    <h2>鼠标和触摸事件</h2>
    
    <!-- 鼠标事件 -->
    <div class="mouse-section">
      <h3>鼠标事件</h3>
      <div 
        class="mouse-area"
        @mousedown="handleMouseDown"
        @mouseup="handleMouseUp"
        @mouseenter="handleMouseEnter"
        @mouseleave="handleMouseLeave"
        @mousemove="handleMouseMove"
        @contextmenu.prevent="handleRightClick"
      >
        鼠标操作区域
        <div class="mouse-info">
          <p>鼠标位置: {{ mousePosition.x }}, {{ mousePosition.y }}</p>
          <p>鼠标状态: {{ mouseStatus }}</p>
        </div>
      </div>
    </div>
    
    <!-- 拖拽功能 -->
    <div class="drag-section">
      <h3>拖拽功能</h3>
      <div 
        class="draggable-box"
        :style="{
          left: boxPosition.x + 'px',
          top: boxPosition.y + 'px',
          cursor: isDragging ? 'grabbing' : 'grab'
        }"
        @mousedown="startDrag"
        @touchstart="startDrag"
      >
        拖拽我
      </div>
    </div>
    
    <!-- 滚轮事件 -->
    <div class="wheel-section">
      <h3>滚轮事件</h3>
      <div 
        class="wheel-area"
        @wheel.prevent="handleWheel"
        :style="{ fontSize: fontSize + 'px' }"
      >
        使用鼠标滚轮调整字体大小
        <p>当前字体大小: {{ fontSize }}px</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const mousePosition = ref({ x: 0, y: 0 })
const mouseStatus = ref('未按下')
const isDragging = ref(false)
const dragStart = ref({ x: 0, y: 0 })
const boxPosition = ref({ x: 50, y: 50 })
const fontSize = ref(16)

const handleMouseDown = (event) => {
  mouseStatus.value = '按下'
  updateMousePosition(event)
}

const handleMouseUp = (event) => {
  mouseStatus.value = '释放'
  updateMousePosition(event)
}

const handleMouseEnter = (event) => {
  mouseStatus.value = '进入'
  updateMousePosition(event)
}

const handleMouseLeave = (event) => {
  mouseStatus.value = '离开'
  updateMousePosition(event)
}

const handleMouseMove = (event) => {
  updateMousePosition(event)
}

const handleRightClick = () => {
  alert('右键菜单被阻止了')
}

const updateMousePosition = (event) => {
  mousePosition.value = {
    x: event.clientX,
    y: event.clientY
  }
}

// 拖拽功能
const startDrag = (event) => {
  isDragging.value = true
  const clientX = event.clientX || event.touches[0].clientX
  const clientY = event.clientY || event.touches[0].clientY
  
  dragStart.value = {
    x: clientX - boxPosition.value.x,
    y: clientY - boxPosition.value.y
  }
  
  document.addEventListener('mousemove', drag)
  document.addEventListener('touchmove', drag)
  document.addEventListener('mouseup', stopDrag)
  document.addEventListener('touchend', stopDrag)
}

const drag = (event) => {
  if (!isDragging.value) return
  
  const clientX = event.clientX || event.touches[0].clientX
  const clientY = event.clientY || event.touches[0].clientY
  
  boxPosition.value = {
    x: clientX - dragStart.value.x,
    y: clientY - dragStart.value.y
  }
}

const stopDrag = () => {
  isDragging.value = false
  document.removeEventListener('mousemove', drag)
  document.removeEventListener('touchmove', drag)
  document.removeEventListener('mouseup', stopDrag)
  document.removeEventListener('touchend', stopDrag)
}

// 滚轮事件
const handleWheel = (event) => {
  if (event.deltaY < 0) {
    fontSize.value = Math.min(fontSize.value + 2, 40)
  } else {
    fontSize.value = Math.max(fontSize.value - 2, 12)
  }
}
</script>

<style>
.mouse-demo {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.mouse-section, .drag-section, .wheel-section {
  margin-bottom: 30px;
  padding: 20px;
  background-color: #fff;
  border: 1px solid #dee2e6;
  border-radius: 8px;
}

.mouse-area {
  width: 100%;
  height: 200px;
  background-color: #e3f2fd;
  border: 2px dashed #2196f3;
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  user-select: none;
}

.mouse-info {
  margin-top: 20px;
  text-align: center;
  background-color: rgba(255, 255, 255, 0.8);
  padding: 10px;
  border-radius: 4px;
}

.drag-section {
  position: relative;
  height: 300px;
  overflow: hidden;
}

.draggable-box {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: #ff6b6b;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: bold;
  user-select: none;
  transition: none;
}

.wheel-area {
  width: 100%;
  height: 200px;
  background-color: #d4edda;
  border: 2px solid #c3e6cb;
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  user-select: none;
}
</style>

注意事项和最佳实践

1. 事件处理中的 this 绑定

vue 复制代码
<template>
  <div class="this-demo">
    <h2>this 绑定问题</h2>
    
    <!-- ❌ 错误示例:箭头函数可能导致 this 问题 -->
    <button @click="wrongHandler">错误处理</button>
    
    <!-- ✅ 正确示例:使用普通函数 -->
    <button @click="correctHandler">正确处理</button>
    
    <!-- ✅ 正确示例:使用箭头函数但不依赖 this -->
    <button @click="() => console.log('箭头函数')">箭头函数</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const message = ref('Hello Vue3!')

// ❌ 可能有问题的处理方式
const wrongHandler = () => {
  // 在 setup 中,this 是 undefined
  console.log(this) // undefined
}

// ✅ 推荐的处理方式
const correctHandler = () => {
  console.log(message.value) // 直接使用响应式变量
}
</script>

2. 事件性能优化

vue 复制代码
<template>
  <div class="performance-demo">
    <h2>事件性能优化</h2>
    
    <!-- ✅ 使用 .once 修饰符 -->
    <button @click.once="handleOnce">只执行一次</button>
    
    <!-- ✅ 使用 .passive 修饰符提高滚动性能 -->
    <div 
      class="scroll-area"
      @scroll.passive="handleScroll"
    >
      <div class="scroll-content">
        <div 
          v-for="item in 100" 
          :key="item"
          class="scroll-item"
        >
          项目 {{ item }}
        </div>
      </div>
    </div>
    
    <!-- ✅ 防抖处理 -->
    <div class="debounce-demo">
      <input 
        @input="debouncedInput"
        placeholder="输入文字(防抖)"
        class="input-field"
      >
      <p>防抖结果: {{ debounceResult }}</p>
    </div>
    
    <!-- ✅ 节流处理 -->
    <div class="throttle-demo">
      <div 
        @mousemove="throttledMouseMove"
        class="throttle-area"
      >
        鼠标移动区域(节流)
        <p>节流结果: {{ throttleResult }}</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const debounceResult = ref('')
const throttleResult = ref('')
let debounceTimer = null
let throttleTimer = null
let lastThrottleTime = 0
const throttleDelay = 100

const handleOnce = () => {
  alert('这个按钮只能点击一次')
}

const handleScroll = (event) => {
  console.log('滚动事件触发')
}

// 防抖处理
const debouncedInput = (event) => {
  clearTimeout(debounceTimer)
  debounceTimer = setTimeout(() => {
    debounceResult.value = event.target.value
  }, 300)
}

// 节流处理
const throttledMouseMove = (event) => {
  const now = Date.now()
  if (now - lastThrottleTime > throttleDelay) {
    throttleResult.value = `X: ${event.clientX}, Y: ${event.clientY}`
    lastThrottleTime = now
  }
}
</script>

<style>
.performance-demo {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.scroll-area {
  height: 200px;
  overflow-y: auto;
  border: 1px solid #ced4da;
  border-radius: 4px;
  margin: 20px 0;
}

.scroll-content {
  padding: 10px;
}

.scroll-item {
  padding: 10px;
  margin: 5px 0;
  background-color: #f8f9fa;
  border-radius: 4px;
}

.debounce-demo, .throttle-demo {
  margin: 20px 0;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
}

.throttle-area {
  height: 150px;
  background-color: #e3f2fd;
  border: 2px dashed #2196f3;
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-top: 10px;
}

.input-field {
  width: 100%;
  padding: 10px;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 16px;
}

button {
  padding: 10px 20px;
  margin: 5px;
  border: none;
  border-radius: 4px;
  background-color: #007bff;
  color: white;
  cursor: pointer;
  font-size: 16px;
}
</style>

总结

事件修饰符速查表

修饰符 作用
.prevent 阻止默认行为
.stop 阻止事件冒泡
.self 只在事件目标是元素自身时触发
.once 只触发一次
.capture 使用事件捕获模式
.passive 以 passive 模式添加事件

按键修饰符速查表

修饰符 对应按键
.enter Enter
.tab Tab
.delete Delete 或 Backspace
.esc Escape
.space 空格
.up 上箭头
.down 下箭头
.left 左箭头
.right 右箭头
.ctrl Ctrl
.alt Alt
.shift Shift
.meta Meta (Mac: ⌘, Windows: ⊞)

使用建议

  1. 优先使用简写@click 而不是 v-on:click
  2. 合理使用修饰符:避免在 JavaScript 中处理本可以用修饰符解决的问题
  3. 注意性能:对于频繁触发的事件使用防抖或节流
  4. 事件委托:对于大量相似元素,考虑在父元素上处理事件
  5. 清理事件:记得在组件销毁时清理手动添加的事件监听器

记忆口诀

  • @click:点击事件最常用
  • @input:输入实时响应
  • @submit.prevent:表单提交防跳转
  • @keyup.enter:回车键触发
  • 修饰符连用:功能组合更强大
相关推荐
Noxi_lumors1 分钟前
VITE BALABALA require balabla not supported
前端·vite
周胜24 分钟前
node-sass
前端
aloha_10 分钟前
Windows 系统中,杀死占用某个端口(如 8080)的进程
前端
牧野星辰10 分钟前
让el-table长个小脑袋,记住我的滚动位置
前端·javascript·element
code_YuJun12 分钟前
React 常见问题
前端
_Congratulate14 分钟前
vue3高德地图api整合封装(自定义撒点、轨迹等)
前端·javascript·vue.js
页面仔Dony36 分钟前
Vue2 与 Vue3 深度对比
vue.js·前端框架
用户9047066835739 分钟前
TL如何进行有效的CR
前端
富婆苗子42 分钟前
关于wangeditor的自定义组件和元素
前端·javascript
顾辰逸you44 分钟前
uniapp--咸虾米壁纸(三)
前端·微信小程序