vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue v-model使用和表单处理

Vue中v-model的使用技巧和表单处理方案

作者:阿猫的故乡

这篇文章详细讲解了v-model的基本用法及其在不同输入类型中的应用,介绍了lazy、number和trim修饰符的使用场景,并展示了如何封装支持v-model的自定义表单组件以及手动校验的完整流程,需要的朋友可以参考下

一、回忆一下 v-model 的本事

前面讲组件通信时我们拆过 v-model,它是一个语法糖,本质是 :value + @input 的组合。在表单元素上,它能让数据和输入框双向绑定

1.1 普通文本框

<template>
  <div>
    <!-- 
      v-model 绑定到 username 这个 ref 上
      用户在输入框里打字,username 自动更新;
      修改 username 的值,输入框也会自动变
    -->
    <input v-model="username" placeholder="输入用户名" />
    <p>你输入的是:{{ username }}</p>
  </div>
</template>
<script setup>
import { ref } from 'vue'
// 定义一个响应式变量存用户名
const username = ref('')
</script>

为什么方便?
如果不写 v-model,你得手动写 :value="username" 和 @input="e => username = e.target.value",麻烦又容易忘。

1.2 各种输入类型的绑定

<template>
  <div>
    <!-- 文本框 -->
    <input v-model="text" placeholder="文本" />
    <!-- 多行文本 -->
    <textarea v-model="textarea" placeholder="多行文本"></textarea>
    <!-- 复选框(单个布尔值) -->
    <input type="checkbox" v-model="checked" /> 同意协议
    <p>是否同意:{{ checked }}</p>
    <!-- 多个复选框(绑定到数组) -->
    <input type="checkbox" v-model="hobbies" value="读书" /> 读书
    <input type="checkbox" v-model="hobbies" value="跑步" /> 跑步
    <input type="checkbox" v-model="hobbies" value="游泳" /> 游泳
    <p>爱好:{{ hobbies }}</p>
    <!-- 单选框 -->
    <input type="radio" v-model="gender" value="男" /> 男
    <input type="radio" v-model="gender" value="女" /> 女
    <p>性别:{{ gender }}</p>
    <!-- 下拉选择框 -->
    <select v-model="city">
      <option value="">请选择城市</option>
      <option value="北京">北京</option>
      <option value="上海">上海</option>
      <option value="广州">广州</option>
    </select>
    <p>城市:{{ city }}</p>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
const textarea = ref('')
const checked = ref(false)
const hobbies = ref([])  // 多个复选框,绑数组
const gender = ref('')
const city = ref('')
</script>

记住:

二、修饰符:帮你在绑定过程中做点手脚

v-model 有几个实用修饰符,用起来非常省事。

2.1.lazy:懒同步

默认情况下 v-model 在每次 input 事件后更新数据。.lazy 会改成在 change 事件后更新(即输入框失去焦点时才同步)。

<input v-model.lazy="msg" placeholder="失去焦点才更新" />
<p>{{ msg }}</p>

什么时候用? 不想每敲一个字就触发请求或校验,等用户输入完整再处理。

2.2.number:自动转数字

输入框里的值默认是字符串。如果你需要数字,可以用 .number 自动转换。

<input v-model.number="age" type="number" />
<p>年龄 + 1:{{ age + 1 }}</p>

不加 .number 时 age 是字符串 "25""25" + 1 会变成 "251"。加了 .number 后 age 就是数字 25

2.3.trim:去除首尾空格

<input v-model.trim="username" />

用户不小心在前面或后面打了空格,提交时自动去掉,非常贴心。

三、封装自定义表单组件

原生的 <input> 直接用没问题,但如果你要封装一个带标签、带校验样式、带错误提示的输入框,就需要自己写组件,并且让父组件能用 v-model 绑定。

3.1 自定义输入框组件

<!-- MyInput.vue -->
<template>
  <div class="my-input">
    <!-- 标签文字 -->
    <label v-if="label">{{ label }}</label>
    <!-- 核心:用 :value + @input 实现 v-model -->
    <input
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
      :type="type"
      :placeholder="placeholder"
      :class="{ error: hasError }"
    />
    <!-- 错误提示 -->
    <span v-if="hasError" class="error-msg">{{ errorMsg }}</span>
  </div>
</template>
<script setup>
// 接收 v-model 绑定值
defineProps({
  modelValue: {
    type: String,
    default: ''
  },
  // 其他配置项
  label: String,      // 标签文字
  type: {
    type: String,
    default: 'text'
  },
  placeholder: String,
  hasError: Boolean,  // 是否显示错误状态
  errorMsg: String    // 错误提示文字
})
// 声明事件
defineEmits(['update:modelValue'])
</script>
<style scoped>
.my-input {
  margin-bottom: 10px;
}
.my-input label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}
.my-input input {
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
  width: 100%;
  box-sizing: border-box;
}
.my-input input.error {
  border-color: red;
}
.error-msg {
  color: red;
  font-size: 12px;
}
</style>

父组件使用:

<template>
  <div>
    <MyInput
      v-model="form.username"
      label="用户名"
      placeholder="请输入用户名"
      :has-error="errors.username"
      error-msg="用户名不能为空"
    />
    <p>绑定值:{{ form.username }}</p>
  </div>
</template>
<script setup>
import { reactive, ref } from 'vue'
import MyInput from './MyInput.vue'
const form = reactive({
  username: ''
})
const errors = reactive({
  username: false
})
// 可以结合 watch 做校验
</script>

四、表单验证:最简单的校验方式

实际项目里,表单提交前必须校验。我们先从最基础的手动校验开始。

4.1 手动校验

<template>
  <div>
    <form @submit.prevent="handleSubmit">
      <!-- 用户名 -->
      <div>
        <label>用户名</label>
        <input v-model.trim="form.username" />
        <span v-if="errors.username" style="color:red;">{{ errors.username }}</span>
      </div>
      <!-- 密码 -->
      <div>
        <label>密码</label>
        <input v-model="form.password" type="password" />
        <span v-if="errors.password" style="color:red;">{{ errors.password }}</span>
      </div>
      <button type="submit">提交</button>
    </form>
  </div>
</template>
<script setup>
import { reactive, ref } from 'vue'
const form = reactive({
  username: '',
  password: ''
})
// 存放错误信息
const errors = reactive({
  username: '',
  password: ''
})
// 校验函数
function validate() {
  let isValid = true
  // 重置错误
  errors.username = ''
  errors.password = ''
  // 校验用户名
  if (!form.username) {
    errors.username = '用户名不能为空'
    isValid = false
  } else if (form.username.length < 3) {
    errors.username = '用户名至少3个字符'
    isValid = false
  }
  // 校验密码
  if (!form.password) {
    errors.password = '密码不能为空'
    isValid = false
  } else if (form.password.length < 6) {
    errors.password = '密码至少6位'
    isValid = false
  }
  return isValid
}
function handleSubmit() {
  if (validate()) {
    alert('提交成功!' + JSON.stringify(form))
    // 这里发请求
  }
}
</script>

流程很简单: 定义校验函数,逐个检查字段,有错就放进 errors 对象里,页面显示对应的错误信息。提交时调用校验,通过了才发请求。

五、实战案例:完整的注册表单

来做一个稍微复杂的注册页面,包含用户名、邮箱、密码、确认密码、手机号。每个字段都有实时校验和提交时校验。

<template>
  <div class="register">
    <h2>用户注册</h2>
    <form @submit.prevent="handleSubmit">
      <!-- 用户名 -->
      <div class="form-item">
        <label>用户名</label>
        <input
          v-model.trim="form.username"
          @blur="validateField('username')"
          :class="{ error: errors.username }"
          placeholder="3-10位字符"
        />
        <span class="error-msg" v-if="errors.username">{{ errors.username }}</span>
      </div>
      <!-- 邮箱 -->
      <div class="form-item">
        <label>邮箱</label>
        <input
          v-model.trim="form.email"
          @blur="validateField('email')"
          :class="{ error: errors.email }"
          placeholder="example@mail.com"
        />
        <span class="error-msg" v-if="errors.email">{{ errors.email }}</span>
      </div>
      <!-- 密码 -->
      <div class="form-item">
        <label>密码</label>
        <input
          v-model="form.password"
          type="password"
          @blur="validateField('password')"
          :class="{ error: errors.password }"
          placeholder="至少6位"
        />
        <span class="error-msg" v-if="errors.password">{{ errors.password }}</span>
      </div>
      <!-- 确认密码 -->
      <div class="form-item">
        <label>确认密码</label>
        <input
          v-model="form.rePassword"
          type="password"
          @blur="validateField('rePassword')"
          :class="{ error: errors.rePassword }"
          placeholder="再次输入密码"
        />
        <span class="error-msg" v-if="errors.rePassword">{{ errors.rePassword }}</span>
      </div>
      <!-- 手机号 -->
      <div class="form-item">
        <label>手机号</label>
        <input
          v-model="form.phone"
          @blur="validateField('phone')"
          :class="{ error: errors.phone }"
          placeholder="11位手机号"
        />
        <span class="error-msg" v-if="errors.phone">{{ errors.phone }}</span>
      </div>
      <!-- 同意协议 -->
      <div class="form-item">
        <label>
          <input type="checkbox" v-model="form.agree" />
          我已阅读并同意《用户协议》
        </label>
        <span class="error-msg" v-if="errors.agree">{{ errors.agree }}</span>
      </div>
      <button type="submit">注册</button>
    </form>
  </div>
</template>
<script setup>
import { reactive } from 'vue'
// 表单数据
const form = reactive({
  username: '',
  email: '',
  password: '',
  rePassword: '',
  phone: '',
  agree: false
})
// 错误对象
const errors = reactive({
  username: '',
  email: '',
  password: '',
  rePassword: '',
  phone: '',
  agree: ''
})
// 单个字段校验规则
function validateField(field) {
  switch (field) {
    case 'username':
      if (!form.username) {
        errors.username = '用户名不能为空'
      } else if (form.username.length < 3 || form.username.length > 10) {
        errors.username = '用户名需3-10位字符'
      } else {
        errors.username = ''
      }
      break
    case 'email':
      if (!form.email) {
        errors.email = '邮箱不能为空'
      } else if (!/^\S+@\S+\.\S+$/.test(form.email)) {
        errors.email = '邮箱格式不正确'
      } else {
        errors.email = ''
      }
      break
    case 'password':
      if (!form.password) {
        errors.password = '密码不能为空'
      } else if (form.password.length < 6) {
        errors.password = '密码至少6位'
      } else {
        errors.password = ''
      }
      // 如果确认密码已填,顺带校验一下是否一致
      if (form.rePassword && form.password !== form.rePassword) {
        errors.rePassword = '两次密码不一致'
      } else if (form.rePassword) {
        errors.rePassword = ''
      }
      break
    case 'rePassword':
      if (!form.rePassword) {
        errors.rePassword = '请确认密码'
      } else if (form.rePassword !== form.password) {
        errors.rePassword = '两次密码不一致'
      } else {
        errors.rePassword = ''
      }
      break
    case 'phone':
      if (!form.phone) {
        errors.phone = '手机号不能为空'
      } else if (!/^1[3-9]\d{9}$/.test(form.phone)) {
        errors.phone = '手机号格式不正确'
      } else {
        errors.phone = ''
      }
      break
  }
}
// 校验全部字段
function validateAll() {
  validateField('username')
  validateField('email')
  validateField('password')
  validateField('rePassword')
  validateField('phone')
  // 协议单独校验
  if (!form.agree) {
    errors.agree = '请同意用户协议'
  } else {
    errors.agree = ''
  }
  // 检查是否有错误
  return Object.values(errors).every(msg => msg === '')
}
// 提交
function handleSubmit() {
  if (validateAll()) {
    alert('注册成功!')
    // 这里发送请求给后端
  }
}
</script>
<style scoped>
.register {
  max-width: 400px;
  margin: 0 auto;
}
.form-item {
  margin-bottom: 15px;
}
.form-item label {
  display: block;
  margin-bottom: 5px;
}
.form-item input[type="text"],
.form-item input[type="password"] {
  width: 100%;
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}
.form-item input.error {
  border-color: red;
}
.error-msg {
  color: red;
  font-size: 12px;
}
button {
  width: 100%;
  padding: 8px;
  background: #409eff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

设计思路:

六、总结

今天我们学了:

这些知识足够你应对大部分项目中的表单需求。如果你想把校验逻辑抽出来复用,还可以结合之前学的组合式函数,封装一个 useFormValidation,这个我们以后可以单独聊。

以上就是Vue中v-model的使用技巧和表单处理方案的详细内容,更多关于Vue v-model使用和表单处理的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文