表单处理

表单控件绑定

在 Vue.js 3.5 中,表单控件绑定是实现用户输入与数据模型同步的核心机制,主要通过 v-model 指令完成。该指令本质上是语法糖,会根据控件类型自动绑定不同的属性和事件,例如文本输入框绑定 value 属性和 input 事件,复选框绑定 checked 属性和 change 事件等 [13]。以下以“用户注册表单”为案例,分类展示不同控件的绑定方式及数据类型特性。

文本框(input/textarea)
文本类控件(单行输入框、多行文本框)通过 v-model 绑定字符串类型数据。例如用户名输入框:

plaintext
复制代码
<template>
  <div class="form-group">
    <label>用户名:</label>
    <input type="text" v-model="username" placeholder="请输入用户名" />
  </div>
  <div class="form-group">
    <label>个人简介:</label>
    <textarea v-model="bio" placeholder="请输入个人简介"></textarea>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const username = ref('') // 字符串类型
const bio = ref('') // 字符串类型
</script>

单选框(radio)
单选框组通过相同的 v-model 绑定,value 为具体选项值,绑定数据为字符串类型(选中项的 value):

plaintext
复制代码
<template>
  <div class="form-group">
    <label>性别:</label>
    <input type="radio" v-model="gender" value="male" id="male" />
    <label for="male">男</label>
    <input type="radio" v-model="gender" value="female" id="female" />
    <label for="female">女</label>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const gender = ref('male') // 字符串类型,默认选中"男"
</script>

多选框(checkbox)

  • 单个复选框:绑定布尔值(checked 状态)
  • 多个复选框组:通过相同 v-model 绑定数组,value 为选项值,数组元素为选中项的 value
plaintext
复制代码
<template>
  <div class="form-group">
    <label>
      <input type="checkbox" v-model="agree" /> 同意用户协议
    </label>
  </div>
  <div class="form-group">
    <label>爱好:</label>
    <input type="checkbox" v-model="hobbies" value="reading" id="reading" />
    <label for="reading">阅读</label>
    <input type="checkbox" v-model="hobbies" value="sports" id="sports" />
    <label for="sports">运动</label>
    <input type="checkbox" v-model="hobbies" value="coding" id="coding" />
    <label for="coding">编程</label>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const agree = ref(false) // 布尔值
const hobbies = ref([]) // 数组类型,存储选中的爱好
</script>

下拉框(select)

  • 单选下拉框:v-model 绑定字符串,optionvalue 为选项值
  • 多选下拉框:添加 multiple 属性,v-model 绑定数组
plaintext
复制代码
<template>
  <div class="form-group">
    <label>城市:</label>
    <select v-model="city">
      <option value="">请选择</option>
      <option value="beijing">北京</option>
      <option value="shanghai">上海</option>
      <option value="guangzhou">广州</option>
    </select>
  </div>
  <div class="form-group">
    <label>技能(可多选):</label>
    <select v-model="skills" multiple>
      <option value="html">HTML</option>
      <option value="css">CSS</option>
      <option value="js">JavaScript</option>
    </select>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const city = ref('') // 字符串类型
const skills = ref([]) // 数组类型(multiple时)
</script>

绑定值类型总结

  • 文本框/单选框/单选下拉框 → 字符串
  • 单个复选框 → 布尔值
  • 多个复选框组/多选下拉框 → 数组

表单验证策略

表单验证是确保用户输入合法性的关键环节,Vue.js 支持多种验证策略,以下结合“用户注册表单”需求,对比三种实现方案。

1. HTML5 原生验证
利用 HTML5 表单属性(如 requiredminlengthpattern)进行基础验证,结合 invalid 事件监听错误:

plaintext
复制代码
<template>
  <form @submit.prevent="handleSubmit">
    <div class="form-group">
      <label>用户名:</label>
      <input 
        type="text" 
        v-model="username" 
        required 
        minlength="3" 
        @invalid="handleInvalid($event, 'username')"
        placeholder="至少3个字符"
      />
      <span class="error-msg">{{ errorMsg.username }}</span>
    </div>
    <div class="form-group">
      <label>手机号:</label>
      <input 
        type="tel" 
        v-model="phone" 
        pattern="^1[3-9]\d{9}$" 
        @invalid="handleInvalid($event, 'phone')"
        placeholder="请输入手机号"
      />
      <span class="error-msg">{{ errorMsg.phone }}</span>
    </div>
    <button type="submit">注册</button>
  </form>
</template>

<script setup>
import { ref } from 'vue'
const username = ref('')
const phone = ref('')
const errorMsg = ref({})

const handleInvalid = (e, field) => {
  e.preventDefault() // 阻止浏览器默认错误提示
  switch(field) {
    case 'username':
      errorMsg.value.username = e.target.validity.valueMissing 
        ? '用户名必填' 
        : '用户名至少3个字符'
      break
    case 'phone':
      errorMsg.value.phone = '请输入合法手机号'
      break
  }
}

const handleSubmit = () => {
  // 触发表单整体验证
  const isValid = document.querySelector('form').checkValidity()
  if (isValid) alert('注册成功!')
}
</script>

2. 自定义验证函数
通过编写 validateForm 函数实现复杂逻辑验证,结合 errorMsg 显示自定义错误信息:

plaintext
复制代码
<template>
  <form @submit.prevent="handleSubmit">
    <div class="form-group">
      <label>用户名:</label>
      <input 
        type="text" 
        v-model="username" 
        @input="validateField('username')"
        placeholder="请输入用户名"
      />
      <span class="error-msg">{{ errorMsg.username }}</span>
    </div>
    <div class="form-group">
      <label>密码:</label>
      <input 
        type="password" 
        v-model="password" 
        @input="validateField('password')"
        placeholder="至少6个字符"
      />
      <span class="error-msg">{{ errorMsg.password }}</span>
    </div>
    <button type="submit">注册</button>
  </form>
</template>

<script setup>
import { ref } from 'vue'
const username = ref('')
const password = ref('')
const errorMsg = ref({})

// 单个字段验证
const validateField = (field) => {
  switch(field) {
    case 'username':
      errorMsg.value.username = username.value ? '' : '用户名不能为空'
      break
    case 'password':
      errorMsg.value.password = password.value.length >=6 ? '' : '密码至少6个字符'
      break
  }
}

// 整体表单验证
const validateForm = () => {
  validateField('username')
  validateField('password')
  return Object.values(errorMsg.value).every(msg => !msg)
}

const handleSubmit = () => {
  if (validateForm()) {
    alert('注册成功!')
  }
}
</script>

3. VeeValidate 声明式验证
引入 VeeValidate 库(Vue 3 推荐使用 v4+ 版本),通过 useFormdefineField 实现声明式验证规则:

plaintext
复制代码
<template>
  <form @submit.prevent="handleSubmit(onSubmit)">
    <div class="form-group">
      <label>用户名:</label>
      <input type="text" v-model="username" />
      <span class="error-msg">{{ errors.username }}</span>
    </div>
    <div class="form-group">
      <label>密码:</label>
      <input type="password" v-model="password" />
      <span class="error-msg">{{ errors.password }}</span>
    </div>
    <button type="submit">注册</button>
  </form>
</template>

<script setup>
import { useForm, defineField } from 'vee-validate'
import { required, minLength } from '@vee-validate/rules'

// 定义验证规则
const { handleSubmit, errors } = useForm({
  initialValues: { username: '', password: '' },
  validationSchema: {
    username: defineField('username', required('用户名必填')),
    password: defineField('password', minLength(6, '密码至少6个字符'))
  }
})

// 提交处理
const onSubmit = (values) => {
  alert('注册成功!', values)
}
</script>

验证触发方式

  • 实时验证:通过 @input 事件或 VeeValidate 的 validateOnInput: true(默认)
  • 提交时验证:在 handleSubmit 中触发整体验证
  • 错误提示 UI:通过 errorMsg 对象或 VeeValidate 的 errors 对象绑定到 DOM,实现错误信息动态显示

自定义表单组件

在实际开发中,经常需要封装通用表单组件(如带标签、验证、错误提示的输入框)。以下以 MyInput 组件为例,说明如何实现支持 v-model 双向绑定及自定义验证的表单组件。

1. 组件原理
Vue 中组件的 v-model 本质是 props: modelValueemit: update:modelValue 的语法糖,即:
<MyInput v-model="username" /> 等价于 <MyInput :modelValue="username" @update:modelValue="username = $event" />

2. MyInput 组件实现
封装支持 labelplaceholder、自定义验证的输入框组件:

plaintext
复制代码
<!-- MyInput.vue -->
<template>
  <div class="my-input">
    <label v-if="label" class="input-label">{{ label }}</label>
    <input
      type="text"
      :placeholder="placeholder"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
      @blur="validate"
    />
    <span class="error-msg" v-if="errorMsg">{{ errorMsg }}</span>
  </div>
</template>

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

// 定义 props
const props = defineProps({
  modelValue: { type: String, required: true },
  label: { type: String, default: '' },
  placeholder: { type: String, default: '' },
  // 自定义验证函数(可选)
  validator: { type: Function, default: () => true }
})

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

// 错误信息
const errorMsg = ref('')

// 验证逻辑
const validate = () => {
  const result = props.validator(props.modelValue)
  errorMsg.value = typeof result === 'string' ? result : ''
}
</script>

<style scoped>
.my-input { margin-bottom: 16px; }
.input-label { display: inline-block; width: 80px; }
input { padding: 8px; width: 200px; }
.error-msg { color: red; font-size: 12px; margin-left: 84px; }
</style>

3. 父组件使用方式
在“用户注册表单”中使用 MyInput,并传入自定义验证(如手机号格式):

plaintext
复制代码
<!-- RegisterForm.vue -->
<template>
  <form @submit.prevent="handleSubmit">
    <MyInput 
      v-model="username" 
      label="用户名" 
      placeholder="请输入用户名"
      :validator="validateUsername"
    />
    <MyInput 
      v-model="phone" 
      label="手机号" 
      placeholder="请输入手机号"
      :validator="validatePhone"
    />
    <button type="submit">注册</button>
  </form>
</template>

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

const username = ref('')
const phone = ref('')

// 用户名验证:非空
const validateUsername = (value) => {
  if (!value) return '用户名不能为空'
  return true
}

// 手机号验证:格式校验
const validatePhone = (value) => {
  if (!value) return '手机号不能为空'
  const reg = /^1[3-9]\d{9}$/
  if (!reg.test(value)) return '手机号格式错误'
  return true
}

const handleSubmit = () => {
  // 触发所有子组件验证(实际项目可通过 refs 实现)
  alert('注册成功!', { username: username.value, phone: phone.value })
}
</script>

3. 组件参数说明

Props 类型 说明 默认值
modelValue String 绑定值(v-model) -
label String 标签文本 -
placeholder String 输入框提示文本 '请输入'
validator Function 自定义验证函数 () => true

自定义验证函数规则

  • 接收参数:当前输入值(value
  • 返回值:true(验证通过)或错误信息字符串(验证失败)
  • 触发时机:组件内部通过 @blur 事件在输入框失焦时触发

通过以上实现,MyInput 组件既支持 v-model 双向绑定,又能灵活接入自定义验证逻辑,可复用於各类表单场景。

总结

表单处理是 Vue.js 开发中的核心场景,主要涉及三方面:

  1. 控件绑定:利用 v-model 根据控件类型绑定不同数据类型(字符串/布尔/数组)
  2. 验证策略:HTML5 原生验证(基础)、自定义函数(灵活)、VeeValidate(声明式)
  3. 自定义组件:通过 modelValue + update:modelValue 实现 v-model,结合 props 与 emit 实现复用与扩展

掌握这些技巧可有效提升表单开发效率,满足从简单到复杂场景的需求。