第一部分:Vue 3 基础入门

第9集:事件处理与表单绑定基础

在Vue应用中,事件处理和表单绑定是非常常见的操作。Vue 3提供了简洁、高效的方式来处理用户交互和表单数据。在本集中,我们将学习Vue 3的事件处理和表单绑定基础,包括事件监听、事件修饰符、表单输入绑定等内容。

9.1 事件处理基础

Vue使用v-on指令(简写为@)来监听DOM事件,并在事件触发时执行相应的处理函数。

9.1.1 基本事件监听

示例

<template>
  <div>
    <h1>事件处理基础</h1>
    <button v-on:click="handleClick">点击我</button>
    <button @click="handleClick">点击我(简写)</button>
    <p>点击次数: {{ count }}</p>
  </div>
</template>

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

const count = ref(0)

function handleClick() {
  count.value++
}
</script>

9.1.2 事件对象

在事件处理函数中,我们可以通过$event访问原生的DOM事件对象。

示例

<template>
  <div>
    <h1>事件对象</h1>
    <button @click="handleClickWithEvent($event)">点击我</button>
    <p>事件类型: {{ eventType }}</p>
    <p>点击坐标: {{ x }}, {{ y }}</p>
  </div>
</template>

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

const eventType = ref('')
const x = ref(0)
const y = ref(0)

function handleClickWithEvent(event) {
  eventType.value = event.type
  x.value = event.clientX
  y.value = event.clientY
}
</script>

9.1.3 传递参数

我们可以在事件处理函数中传递自定义参数,同时也可以访问事件对象。

示例

<template>
  <div>
    <h1>传递参数</h1>
    <button @click="handleClickWithParams('hello', $event)">点击我</button>
    <p>参数: {{ message }}</p>
  </div>
</template>

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

const message = ref('')

function handleClickWithParams(param, event) {
  message.value = param
  console.log('Event:', event.type)
}
</script>

9.2 事件修饰符

Vue提供了事件修饰符来处理常见的DOM事件行为,如阻止冒泡、阻止默认行为等。事件修饰符可以链式使用。

9.2.1 常用事件修饰符

  • .stop:阻止事件冒泡
  • .prevent:阻止默认行为
  • .capture:使用事件捕获模式
  • .self:只当事件目标是元素本身时触发
  • .once:事件只触发一次
  • .passive:告诉浏览器事件监听器不会调用preventDefault()

示例

<template>
  <div>
    <h1>事件修饰符</h1>
    
    <!-- 阻止冒泡 -->
    <div class="outer" @click="handleOuterClick">
      <div class="inner" @click.stop="handleInnerClick">
        点击内层div(阻止冒泡)
      </div>
    </div>
    <p>外层点击次数: {{ outerCount }}</p>
    <p>内层点击次数: {{ innerCount }}</p>
    
    <!-- 阻止默认行为 -->
    <a href="https://example.com" @click.prevent="handleLinkClick">点击链接(阻止跳转)</a>
    <p>链接点击次数: {{ linkCount }}</p>
    
    <!-- 只触发一次 -->
    <button @click.once="handleOnceClick">点击我(只触发一次)</button>
    <p>一次性按钮点击次数: {{ onceCount }}</p>
    
    <!-- 链式修饰符 -->
    <form @submit.prevent.stop="handleSubmit">
      <button type="submit">提交表单(阻止默认行为和冒泡)</button>
    </form>
    <p>表单提交次数: {{ submitCount }}</p>
  </div>
</template>

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

const outerCount = ref(0)
const innerCount = ref(0)
const linkCount = ref(0)
const onceCount = ref(0)
const submitCount = ref(0)

function handleOuterClick() {
  outerCount.value++
}

function handleInnerClick() {
  innerCount.value++
}

function handleLinkClick() {
  linkCount.value++
}

function handleOnceClick() {
  onceCount.value++
}

function handleSubmit() {
  submitCount.value++
}
</script>

<style scoped>
.outer {
  width: 200px;
  height: 200px;
  background-color: #f0f0f0;
  padding: 20px;
  margin: 20px 0;
}

.inner {
  width: 100px;
  height: 100px;
  background-color: #42b883;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
</style>

9.2.2 按键修饰符

Vue提供了按键修饰符来监听特定的键盘事件。

常用按键修饰符

  • .enter:回车键
  • .tab:Tab键
  • .delete:删除键和退格键
  • .esc:Esc键
  • .space:空格键
  • .up:上箭头
  • .down:下箭头
  • .left:左箭头
  • .right:右箭头

示例

<template>
  <div>
    <h1>按键修饰符</h1>
    <input 
      type="text" 
      @keyup.enter="handleEnter" 
      @keyup.esc="handleEsc" 
      v-model="inputValue" 
      placeholder="按下Enter或Esc键"
    >
    <p>输入内容: {{ inputValue }}</p>
    <p>Enter键按下次数: {{ enterCount }}</p>
    <p>Esc键按下次数: {{ escCount }}</p>
  </div>
</template>

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

const inputValue = ref('')
const enterCount = ref(0)
const escCount = ref(0)

function handleEnter() {
  enterCount.value++
}

function handleEsc() {
  escCount.value++
  inputValue.value = '' // 清空输入
}
</script>

9.3 表单输入绑定

Vue提供了v-model指令来实现表单输入和应用状态之间的双向绑定。

9.3.1 基本用法

示例

<template>
  <div>
    <h1>表单输入绑定</h1>
    
    <!-- 文本输入 -->
    <div>
      <label for="name">姓名:</label>
      <input type="text" id="name" v-model="name">
      <p>姓名: {{ name }}</p>
    </div>
    
    <!-- 多行文本 -->
    <div>
      <label for="message">留言:</label>
      <textarea id="message" v-model="message" rows="3"></textarea>
      <p>留言: {{ message }}</p>
      <p>字数: {{ message.length }}</p>
    </div>
    
    <!-- 复选框 -->
    <div>
      <label>
        <input type="checkbox" v-model="isAgreed">
        我同意条款
      </label>
      <p>是否同意: {{ isAgreed ? '是' : '否' }}</p>
    </div>
    
    <!-- 多个复选框 -->
    <div>
      <h3>选择爱好:</h3>
      <label>
        <input type="checkbox" v-model="hobbies" value="reading">
        阅读
      </label>
      <label>
        <input type="checkbox" v-model="hobbies" value="sports">
        运动
      </label>
      <label>
        <input type="checkbox" v-model="hobbies" value="music">
        音乐
      </label>
      <p>选择的爱好: {{ hobbies }}</p>
    </div>
    
    <!-- 单选按钮 -->
    <div>
      <h3>选择性别:</h3>
      <label>
        <input type="radio" v-model="gender" value="male">
        男
      </label>
      <label>
        <input type="radio" v-model="gender" value="female">
        女
      </label>
      <p>选择的性别: {{ gender }}</p>
    </div>
    
    <!-- 下拉选择 -->
    <div>
      <label for="city">选择城市:</label>
      <select id="city" v-model="city">
        <option value="">请选择</option>
        <option value="beijing">北京</option>
        <option value="shanghai">上海</option>
        <option value="guangzhou">广州</option>
        <option value="shenzhen">深圳</option>
      </select>
      <p>选择的城市: {{ city }}</p>
    </div>
    
    <!-- 数字输入 -->
    <div>
      <label for="age">年龄:</label>
      <input type="number" id="age" v-model.number="age">
      <p>年龄: {{ age }}</p>
      <p>年龄类型: {{ typeof age }}</p>
    </div>
  </div>
</template>

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

// 文本输入
const name = ref('')
const message = ref('')

// 复选框
const isAgreed = ref(false)
const hobbies = ref([])

// 单选按钮
const gender = ref('')

// 下拉选择
const city = ref('')

// 数字输入
const age = ref(0)
</script>

9.3.2 v-model修饰符

Vue提供了v-model修饰符来处理表单输入的特殊情况。

常用修饰符

  • .lazy:在change事件而不是input事件中更新数据
  • .number:自动将输入转换为数字
  • .trim:自动去除输入内容的首尾空格

示例

<template>
  <div>
    <h1>v-model修饰符</h1>
    
    <!-- lazy修饰符 -->
    <div>
      <label for="lazy-input">Lazy输入:</label>
      <input type="text" id="lazy-input" v-model.lazy="lazyValue">
      <p>输入内容: {{ lazyValue }}</p>
      <p>(失去焦点或按下Enter键后更新)</p>
    </div>
    
    <!-- number修饰符 -->
    <div>
      <label for="number-input">数字输入:</label>
      <input type="text" id="number-input" v-model.number="numberValue">
      <p>输入内容: {{ numberValue }}</p>
      <p>类型: {{ typeof numberValue }}</p>
    </div>
    
    <!-- trim修饰符 -->
    <div>
      <label for="trim-input">Trim输入:</label>
      <input type="text" id="trim-input" v-model.trim="trimValue">
      <p>输入内容: "{{ trimValue }}"</p>
      <p>长度: {{ trimValue.length }}</p>
    </div>
  </div>
</template>

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

const lazyValue = ref('')
const numberValue = ref(0)
const trimValue = ref('')
</script>

9.4 自定义事件与v-model

我们可以在自定义组件中使用v-model,通过definePropsdefineEmits来实现双向绑定。

示例

<template>
  <div>
    <h1>自定义组件的v-model</h1>
    <custom-input v-model="customValue" placeholder="输入内容"></custom-input>
    <p>自定义输入值: {{ customValue }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import CustomInput from './components/CustomInput.vue'

const customValue = ref('')
</script>

CustomInput.vue组件

<template>
  <div class="custom-input">
    <input 
      type="text" 
      :value="modelValue" 
      @input="handleInput" 
      :placeholder="placeholder"
    >
  </div>
</template>

<script setup>
// 定义props
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  },
  placeholder: {
    type: String,
    default: ''
  }
})

// 定义emits
const emit = defineEmits(['update:modelValue'])

// 事件处理
function handleInput(event) {
  emit('update:modelValue', event.target.value)
}
</script>

<style scoped>
.custom-input input {
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  width: 300px;
}
</style>

9.5 表单处理最佳实践

  1. 使用v-model进行双向绑定

    • 避免手动操作DOM来获取表单值
    • 利用v-model的修饰符简化数据处理
  2. 合理使用事件修饰符

    • 使用.prevent阻止表单默认提交
    • 使用.stop阻止不必要的事件冒泡
    • 使用按键修饰符处理键盘事件
  3. 表单验证

    • 在提交前进行客户端验证
    • 使用计算属性或自定义验证函数
    • 考虑使用VeeValidate等表单验证库
  4. 处理表单提交

    • 使用@submit.prevent处理表单提交
    • 在提交函数中处理异步请求
    • 显示加载状态和错误信息
  5. 优化用户体验

    • 提供即时反馈
    • 使用适当的输入类型(如email、tel等)
    • 添加占位符和提示文本

9.6 综合示例:待办事项列表

让我们创建一个综合示例,使用事件处理和表单绑定来实现一个简单的待办事项列表。

示例

<template>
  <div class="todo-app">
    <h1>待办事项列表</h1>
    
    <!-- 添加待办事项 -->
    <form @submit.prevent="addTodo">
      <input 
        type="text" 
        v-model="newTodo" 
        placeholder="添加新的待办事项"
        required
      >
      <button type="submit">添加</button>
    </form>
    
    <!-- 待办事项列表 -->
    <ul class="todo-list">
      <li 
        v-for="todo in todos" 
        :key="todo.id"
        :class="{ completed: todo.completed }"
      >
        <input 
          type="checkbox" 
          v-model="todo.completed"
          @change="saveTodos"
        >
        <span>{{ todo.text }}</span>
        <button @click="deleteTodo(todo.id)">删除</button>
      </li>
    </ul>
    
    <!-- 统计信息 -->
    <div class="stats">
      <p>总数量: {{ todos.length }}</p>
      <p>已完成: {{ completedCount }}</p>
      <p>未完成: {{ remainingCount }}</p>
    </div>
    
    <!-- 清除已完成 -->
    <button @click="clearCompleted" :disabled="completedCount === 0">
      清除已完成
    </button>
  </div>
</template>

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

// 待办事项列表
const todos = ref([
  { id: 1, text: '学习Vue 3', completed: false },
  { id: 2, text: '创建待办事项应用', completed: false },
  { id: 3, text: '提交代码', completed: false }
])

// 新待办事项
const newTodo = ref('')

// 计算属性
const completedCount = computed(() => {
  return todos.value.filter(todo => todo.completed).length
})

const remainingCount = computed(() => {
  return todos.value.filter(todo => !todo.completed).length
})

// 方法
function addTodo() {
  if (newTodo.value.trim()) {
    todos.value.push({
      id: Date.now(),
      text: newTodo.value.trim(),
      completed: false
    })
    newTodo.value = ''
    saveTodos()
  }
}

function deleteTodo(id) {
  todos.value = todos.value.filter(todo => todo.id !== id)
  saveTodos()
}

function clearCompleted() {
  todos.value = todos.value.filter(todo => !todo.completed)
  saveTodos()
}

function saveTodos() {
  // 这里可以保存到localStorage或发送到服务器
  localStorage.setItem('todos', JSON.stringify(todos.value))
}

// 初始化时从localStorage加载数据
const savedTodos = localStorage.getItem('todos')
if (savedTodos) {
  todos.value = JSON.parse(savedTodos)
}
</script>

<style scoped>
.todo-app {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

form {
  display: flex;
  margin-bottom: 20px;
}

input[type="text"] {
  flex: 1;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  margin-right: 10px;
}

button {
  padding: 8px 16px;
  background-color: #42b883;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #3aa876;
}

button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}

.todo-list {
  list-style: none;
  padding: 0;
  margin-bottom: 20px;
}

.todo-list li {
  display: flex;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.todo-list li.completed span {
  text-decoration: line-through;
  color: #999;
}

.todo-list li span {
  flex: 1;
  margin: 0 10px;
}

.stats {
  display: flex;
  justify-content: space-between;
  margin-bottom: 20px;
  font-size: 14px;
  color: #666;
}
</style>

本集小结

在本集中,我们学习了Vue 3的事件处理和表单绑定基础:

  • 事件处理

    • 使用v-on@监听事件
    • 访问事件对象和传递参数
    • 使用事件修饰符处理常见的DOM行为
    • 使用按键修饰符处理键盘事件
  • 表单输入绑定

    • 使用v-model实现双向绑定
    • 支持文本、多行文本、复选框、单选按钮、下拉选择等
    • 使用v-model修饰符(.lazy.number.trim
    • 在自定义组件中使用v-model
  • 最佳实践

    • 合理使用v-model和事件修饰符
    • 进行表单验证
    • 优化用户体验
    • 处理表单提交和异步请求
  • 综合示例

    • 实现了一个待办事项列表,包含添加、删除、标记完成等功能
    • 使用了localStorage进行数据持久化

事件处理和表单绑定是Vue应用开发中的核心功能,掌握好这些知识可以帮助我们创建交互丰富、用户体验良好的应用。在下一集中,我们将学习Vue 3的条件渲染与列表渲染,进一步提升我们的Vue开发能力。

« 上一篇 响应式数据初体验 下一篇 » 条件渲染与列表渲染入门