第2章 Vue.js基础语法

第5节:事件处理与表单绑定

在Vue.js中,事件处理和表单绑定是构建交互应用的重要功能。本节我们将深入学习Vue的事件处理机制和v-model双向绑定原理。

2.5.1 事件处理机制

Vue提供了多种事件处理方式,包括内联事件处理器和方法事件处理器,以及丰富的事件修饰符。

1. 内联事件处理器

内联事件处理器是直接在模板中定义的事件处理函数:

<template>
  <!-- 简单内联事件处理器 -->
  <button @click="count++">点击增加: {{ count }}</button>
  
  <!-- 传递参数的内联事件处理器 -->
  <button @click="increment(5)">增加5: {{ count }}</button>
  
  <!-- 访问事件对象的内联事件处理器 -->
  <button @click="incrementWithEvent($event, 10)">增加10并访问事件: {{ count }}</button>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment(amount) {
      this.count += amount
    },
    incrementWithEvent(event, amount) {
      console.log('事件对象:', event)
      this.count += amount
    }
  }
}
</script>

2. 方法事件处理器

方法事件处理器是将事件处理逻辑定义在组件的methods选项中:

<template>
  <!-- 方法事件处理器 -->
  <button @click="handleClick">点击事件</button>
  
  <!-- 表单提交事件 -->
  <form @submit="handleSubmit">
    <input type="text" v-model="message">
    <button type="submit">提交</button>
  </form>
  
  <!-- 键盘事件 -->
  <input @keyup="handleKeyUp" placeholder="按任意键">
</template>

<script>
export default {
  data() {
    return {
      message: ''
    }
  },
  methods: {
    handleClick() {
      console.log('按钮被点击')
    },
    handleSubmit(event) {
      event.preventDefault() // 阻止默认提交行为
      console.log('表单提交:', this.message)
    },
    handleKeyUp(event) {
      console.log('按键:', event.key)
    }
  }
}
</script>

3. 事件修饰符:.stop.prevent.capture

Vue提供了多种事件修饰符,用于简化事件处理逻辑:

<template>
  <!-- 阻止事件冒泡 -->
  <div @click="handleParentClick" class="parent">
    <button @click.stop="handleChildClick">阻止冒泡</button>
  </div>
  
  <!-- 阻止默认行为 -->
  <a @click.prevent href="https://vuejs.org/">阻止默认跳转</a>
  <form @submit.prevent="handleSubmit">
    <button type="submit">阻止默认提交</button>
  </form>
  
  <!-- 捕获模式 -->
  <div @click.capture="handleCaptureClick" class="parent">
    <button @click="handleClick">捕获模式</button>
  </div>
  
  <!-- 只触发一次 -->
  <button @click.once="handleOnceClick">只触发一次</button>
  
  <!-- 阻止默认行为并阻止冒泡 -->
  <a @click.stop.prevent href="#">阻止默认和冒泡</a>
  
  <!-- 不触发事件处理函数的默认行为 -->
  <div @click.self="handleSelfClick" class="parent">
    <button @click="handleClick">只有点击自身才触发</button>
  </div>
</template>

<script>
export default {
  methods: {
    handleParentClick() {
      console.log('父元素被点击')
    },
    handleChildClick() {
      console.log('子元素被点击')
    },
    handleSubmit() {
      console.log('表单提交')
    },
    handleCaptureClick() {
      console.log('捕获模式点击')
    },
    handleClick() {
      console.log('正常点击')
    },
    handleOnceClick() {
      console.log('只触发一次')
    },
    handleSelfClick() {
      console.log('点击自身')
    }
  }
}
</script>

<style>
.parent {
  padding: 20px;
  background-color: #f0f0f0;
  margin: 10px 0;
}
</style>

常用事件修饰符

修饰符 作用
.stop 阻止事件冒泡
.prevent 阻止默认行为
.capture 使用事件捕获模式
.self 只在事件目标是元素自身时触发
.once 事件只触发一次
.passive 告诉浏览器事件处理函数不会调用preventDefault(),优化滚动性能

4. 按键修饰符与系统修饰键

Vue提供了按键修饰符,用于处理键盘事件:

<template>
  <!-- 按键修饰符 -->
  <input @keyup.enter="handleEnter" placeholder="按回车提交">
  <input @keyup.esc="handleEsc" placeholder="按ESC清空">
  <input @keyup.space="handleSpace" placeholder="按空格">
  
  <!-- 系统修饰键 -->
  <button @click.ctrl="handleCtrlClick">Ctrl+点击</button>
  <button @click.alt="handleAltClick">Alt+点击</button>
  <button @click.shift="handleShiftClick">Shift+点击</button>
  <button @click.meta="handleMetaClick">Meta+点击</button>
  
  <!-- 组合键 -->
  <button @click.ctrl.alt="handleCtrlAltClick">Ctrl+Alt+点击</button>
  <input @keyup.ctrl.enter="handleCtrlEnter" placeholder="Ctrl+Enter提交">
  
  <!-- 精确修饰符 -->
  <button @click.exact="handleExactClick">只有点击</button>
  <button @click.ctrl.exact="handleCtrlExactClick">只有Ctrl+点击</button>
</template>

<script>
export default {
  methods: {
    handleEnter() {
      console.log('回车键被按下')
    },
    handleEsc() {
      console.log('ESC键被按下')
    },
    handleSpace() {
      console.log('空格键被按下')
    },
    handleCtrlClick() {
      console.log('Ctrl+点击')
    },
    handleAltClick() {
      console.log('Alt+点击')
    },
    handleShiftClick() {
      console.log('Shift+点击')
    },
    handleMetaClick() {
      console.log('Meta+点击')
    },
    handleCtrlAltClick() {
      console.log('Ctrl+Alt+点击')
    },
    handleCtrlEnter() {
      console.log('Ctrl+Enter提交')
    },
    handleExactClick() {
      console.log('只有点击')
    },
    handleCtrlExactClick() {
      console.log('只有Ctrl+点击')
    }
  }
}
</script>

常用按键修饰符

  • .enter
  • .tab
  • .delete (捕获删除和退格键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

系统修饰键

  • .ctrl
  • .alt
  • .shift
  • .meta (Windows键或Mac上的Command键)
  • .exact (精确匹配修饰键组合)

2.5.2 v-model双向绑定原理

v-model指令用于在表单元素上创建双向数据绑定,它会根据控件类型自动选择正确的方法来更新元素。

1. v-model的基本使用

<template>
  <!-- 文本输入 -->
  <div>
    <label>文本输入:</label>
    <input v-model="text" type="text">
    <p>输出:{{ text }}</p>
  </div>
  
  <!-- 多行文本 -->
  <div>
    <label>多行文本:</label>
    <textarea v-model="textarea" rows="4"></textarea>
    <p>输出:{{ textarea }}</p>
  </div>
  
  <!-- 复选框 -->
  <div>
    <label>单个复选框:</label>
    <input v-model="checked" type="checkbox">
    <p>输出:{{ checked }}</p>
  </div>
  
  <!-- 多个复选框绑定到数组 -->
  <div>
    <label>多个复选框:</label>
    <div>
      <input v-model="checkedFruits" type="checkbox" value="apple"> 苹果
      <input v-model="checkedFruits" type="checkbox" value="banana"> 香蕉
      <input v-model="checkedFruits" type="checkbox" value="orange"> 橙子
    </div>
    <p>输出:{{ checkedFruits }}</p>
  </div>
  
  <!-- 单选按钮 -->
  <div>
    <label>单选按钮:</label>
    <div>
      <input v-model="picked" type="radio" value="A"> 选项A
      <input v-model="picked" type="radio" value="B"> 选项B
      <input v-model="picked" type="radio" value="C"> 选项C
    </div>
    <p>输出:{{ picked }}</p>
  </div>
  
  <!-- 下拉选择 -->
  <div>
    <label>下拉选择:</label>
    <select v-model="selected">
      <option value="">请选择</option>
      <option value="vue">Vue.js</option>
      <option value="react">React</option>
      <option value="angular">Angular</option>
    </select>
    <p>输出:{{ selected }}</p>
  </div>
  
  <!-- 多选下拉 -->
  <div>
    <label>多选下拉:</label>
    <select v-model="selectedFrameworks" multiple>
      <option value="vue">Vue.js</option>
      <option value="react">React</option>
      <option value="angular">Angular</option>
    </select>
    <p>输出:{{ selectedFrameworks }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      text: '',
      textarea: '',
      checked: false,
      checkedFruits: [],
      picked: '',
      selected: '',
      selectedFrameworks: []
    }
  }
}
</script>

2. v-model的原理

v-model实际上是一个语法糖,它会根据不同的表单元素展开为不同的事件监听和属性绑定:

<!-- 文本输入的v-model -->
<input v-model="message" type="text">

<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value" type="text">

<!-- 复选框的v-model -->
<input v-model="checked" type="checkbox">

<!-- 等价于 -->
<input :checked="checked" @change="checked = $event.target.checked" type="checkbox">

<!-- 单选按钮的v-model -->
<input v-model="picked" type="radio" value="A">

<!-- 等价于 -->
<input :checked="picked === 'A'" @change="picked = $event.target.value" type="radio" value="A">

<!-- 下拉选择的v-model -->
<select v-model="selected">
  <option value="A">选项A</option>
</select>

<!-- 等价于 -->
<select :value="selected" @change="selected = $event.target.value">
  <option value="A">选项A</option>
</select>

2.5.3 表单修饰符

Vue提供了表单修饰符,用于修改v-model的默认行为:

<template>
  <!-- .lazy修饰符:change事件触发 -->
  <div>
    <label>.lazy修饰符:</label>
    <input v-model.lazy="lazyText" type="text">
    <p>输出:{{ lazyText }}</p>
  </div>
  
  <!-- .number修饰符:自动转为数字 -->
  <div>
    <label>.number修饰符:</label>
    <input v-model.number="numberValue" type="text">
    <p>输出:{{ numberValue }},类型:{{ typeof numberValue }}</p>
  </div>
  
  <!-- .trim修饰符:去除首尾空格 -->
  <div>
    <label>.trim修饰符:</label>
    <input v-model.trim="trimmedText" type="text">
    <p>输出:{{ trimmedText }},长度:{{ trimmedText.length }}</p>
  </div>
  
  <!-- 修饰符组合使用 -->
  <div>
    <label>修饰符组合:</label>
    <input v-model.lazy.trim="combinedText" type="text">
    <p>输出:{{ combinedText }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      lazyText: '',
      numberValue: 0,
      trimmedText: '',
      combinedText: ''
    }
  }
}
</script>

表单修饰符说明

  • .lazy:将input事件改为change事件触发,只有在失去焦点或按回车键时才更新数据
  • .number:自动将用户输入转换为数字类型
  • .trim:自动去除用户输入的首尾空格

2.5.4 表单验证基础实现

表单验证是Web应用中的重要功能,Vue提供了基础的表单验证机制:

<template>
  <form @submit.prevent="handleSubmit" class="validation-form">
    <!-- 用户名验证 -->
    <div class="form-group">
      <label for="username">用户名:</label>
      <input
        id="username"
        v-model="form.username"
        type="text"
        placeholder="请输入用户名"
        @blur="validateUsername"
      >
      <span v-if="errors.username" class="error">{{ errors.username }}</span>
    </div>
    
    <!-- 邮箱验证 -->
    <div class="form-group">
      <label for="email">邮箱:</label>
      <input
        id="email"
        v-model="form.email"
        type="email"
        placeholder="请输入邮箱"
        @blur="validateEmail"
      >
      <span v-if="errors.email" class="error">{{ errors.email }}</span>
    </div>
    
    <!-- 密码验证 -->
    <div class="form-group">
      <label for="password">密码:</label>
      <input
        id="password"
        v-model="form.password"
        type="password"
        placeholder="请输入密码"
        @blur="validatePassword"
      >
      <span v-if="errors.password" class="error">{{ errors.password }}</span>
    </div>
    
    <!-- 确认密码验证 -->
    <div class="form-group">
      <label for="confirmPassword">确认密码:</label>
      <input
        id="confirmPassword"
        v-model="form.confirmPassword"
        type="password"
        placeholder="请确认密码"
        @blur="validateConfirmPassword"
      >
      <span v-if="errors.confirmPassword" class="error">{{ errors.confirmPassword }}</span>
    </div>
    
    <!-- 提交按钮 -->
    <button type="submit" :disabled="!isFormValid">提交</button>
  </form>
</template>

<script>
export default {
  data() {
    return {
      form: {
        username: '',
        email: '',
        password: '',
        confirmPassword: ''
      },
      errors: {
        username: '',
        email: '',
        password: '',
        confirmPassword: ''
      }
    }
  },
  computed: {
    // 表单是否有效
    isFormValid() {
      return Object.values(this.errors).every(error => error === '') &&
             Object.values(this.form).every(value => value.trim() !== '')
    }
  },
  methods: {
    // 验证用户名
    validateUsername() {
      if (!this.form.username.trim()) {
        this.errors.username = '用户名不能为空'
      } else if (this.form.username.length < 3) {
        this.errors.username = '用户名长度不能少于3个字符'
      } else {
        this.errors.username = ''
      }
    },
    // 验证邮箱
    validateEmail() {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
      if (!this.form.email.trim()) {
        this.errors.email = '邮箱不能为空'
      } else if (!emailRegex.test(this.form.email)) {
        this.errors.email = '请输入有效的邮箱地址'
      } else {
        this.errors.email = ''
      }
    },
    // 验证密码
    validatePassword() {
      if (!this.form.password) {
        this.errors.password = '密码不能为空'
      } else if (this.form.password.length < 6) {
        this.errors.password = '密码长度不能少于6个字符'
      } else {
        this.errors.password = ''
      }
    },
    // 验证确认密码
    validateConfirmPassword() {
      if (!this.form.confirmPassword) {
        this.errors.confirmPassword = '请确认密码'
      } else if (this.form.confirmPassword !== this.form.password) {
        this.errors.confirmPassword = '两次输入的密码不一致'
      } else {
        this.errors.confirmPassword = ''
      }
    },
    // 表单提交
    handleSubmit() {
      // 提交前验证所有字段
      this.validateUsername()
      this.validateEmail()
      this.validatePassword()
      this.validateConfirmPassword()
      
      if (this.isFormValid) {
        console.log('表单提交成功:', this.form)
        // 这里可以添加实际的表单提交逻辑
        alert('表单提交成功!')
      } else {
        console.log('表单验证失败')
      }
    }
  }
}
</script>

<style>
.validation-form {
  max-width: 400px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

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

label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 3px;
}

.error {
  display: block;
  color: red;
  font-size: 12px;
  margin-top: 5px;
}

button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}

button:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}
</style>

表单验证的最佳实践

  1. 即时验证:在用户输入过程中或失去焦点时进行验证
  2. 清晰的错误信息:提供具体、友好的错误提示
  3. 视觉反馈:使用颜色、图标等视觉元素提示验证状态
  4. 提交前验证:在表单提交前再次验证所有字段
  5. 禁用无效表单:当表单无效时,禁用提交按钮
  6. 使用专门的表单验证库:对于复杂表单,可以考虑使用VeeValidate、Vuelidate等库

本章小结

在本节中,我们学习了Vue的事件处理与表单绑定:

  • 事件处理机制

    • 内联事件处理器和方法事件处理器
    • 事件修饰符:.stop.prevent.capture
    • 按键修饰符与系统修饰键
    • 组合键和精确修饰符
  • v-model双向绑定原理

    • 不同表单元素的v-model实现
    • v-model的语法糖原理
    • 自定义组件的v-model实现
  • 表单修饰符

    • .lazy:change事件触发
    • .number:自动转为数字
    • .trim:去除首尾空格
    • 修饰符组合使用
  • 表单验证基础实现

    • 即时验证和提交前验证
    • 表单验证的最佳实践
    • 基础表单验证的实现

掌握这些内容对于构建交互丰富的Vue应用至关重要,它们是Vue开发中的核心功能。

« 上一篇 第4节:条件与列表渲染 下一篇 » 单文件组件(SFC)