Vue3 + Element Plus 表单校验实战:规则复用、自定义校验、提示语统一,告别混乱避坑|表单与表格规范篇

【Vue3+Element Plus】表单校验实战:从规则复用、自定义校验到提示语统一,告别校验混乱与高频踩坑!

📑 文章目录


同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。

(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)

很多前端开发者都会遇到一个瓶颈:

代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。

想写出干净、优雅、可维护 的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验

这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。

帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。


一、开篇:为什么需要「表单校验规范」?

在 Vue 项目里,表单校验几乎是每个页面都会遇到的事。不少人会用 el-forma-form 等组件库自带的校验,但往往出现这些问题:

  • 同一个校验规则(如手机号、邮箱)到处复制粘贴,改一处要改多处
  • 自定义校验逻辑和内置规则混在一起,读代码要来回跳转
  • 提示语风格不统一,有的「请输入」,有的「不能为空」,有的英文
  • 校验不触发、校验不生效、异步校验不知道怎么处理

本文的目标是:帮你建立一套「规则可复用、自定义逻辑清晰、提示语统一、不易踩坑」的表单校验规范,适合:

  • 已经会写 JS,但对表单校验概念还不清晰的同学
  • 想系统补基础、顺带校准习惯的熟手

下面从「怎么组织规则」→「怎么自定义校验」→「怎么统一提示语」→「常见坑」逐层展开。

[⬆ 返回目录](#⬆ 返回目录)

二、Vue3 + Element Plus 表单校验基础扫盲

2.1 表单校验的两种常见写法

Vue3 里通常用 Element Plus 的 el-form 做表单,校验有两种方式:

方式一:在模板里写规则(不推荐)

html 复制代码
<template>
  <el-form :model="form" :rules="rules">
    <el-form-item label="用户名" prop="username">
      <el-input v-model="form.username" />
    </el-form-item>
  </el-form>
</template>

<script setup>
const form = reactive({ username: '' })

// 规则写在组件里,复用困难
const rules = {
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
  ]
}
</script>

问题:规则和业务耦合,别的表单想复用只能再写一份。

方式二:规则抽取到单独模块(推荐)

把规则集中在一个文件里,表单只引用,方便复用和维护。

js 复制代码
// src/utils/validateRules.js

/** 必填校验 */
export const required = (message = '此项为必填项') => ({
  required: true,
  message,
  trigger: 'blur'
})

/** 用户名:2-20 位 */
export const username = [
  required('请输入用户名'),
  { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
]

/** 手机号 */
export const phone = [
  required('请输入手机号'),
  { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
]

表单组件里直接引用:

html 复制代码
<script setup>
import { username } from '@/utils/validateRules'

const rules = {
  username
}
</script>

后面要改「手机号」「用户名」规则,只改一个地方即可。

[⬆ 返回目录](#⬆ 返回目录)

2.2 trigger:什么时候触发校验?

trigger 决定何时执行校验:

trigger 值 含义 适用场景
blur 失去焦点 一般输入框、下拉框
change 值改变 选择器、开关、单选框
['blur', 'change'] 两种都触发 既要即时反馈又要失焦校验时

建议:普通输入框用 blur,下拉/选择器用 change,避免频繁校验打断输入。

[⬆ 返回目录](#⬆ 返回目录)

三、规则复用:如何设计可复用的校验规则?

3.1 规则工厂函数

通过「工厂函数」生成规则,可以传入不同提示语或参数:

js 复制代码
// src/utils/validateRules.js

/**
 * 生成「必填」规则
 * @param {string} [message='此项为必填项'] 自定义提示语
 */
export const required = (message = '此项为必填项') => ({
  required: true,
  message,
  trigger: 'blur'
})

/**
 * 生成「长度范围」规则
 */
export const lengthRange = (min, max, message) => ({
  min,
  max,
  message: message || `长度应在 ${min}~${max} 个字符之间`,
  trigger: 'blur'
})

/**
 * 生成「正则」规则
 */
export const pattern = (reg, message) => ({
  pattern: reg,
  message,
  trigger: 'blur'
})

// ============ 预定义规则 ============

/** 手机号 */
export const phone = [
  required('请输入手机号'),
  pattern(/^1[3-9]\d{9}$/, '请输入正确的手机号')
]

/** 邮箱 */
export const email = [
  required('请输入邮箱'),
  pattern(
    /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
    '请输入正确的邮箱地址'
  )
]

/** 用户名:2-20 位 */
export const username = [
  required('请输入用户名'),
  lengthRange(2, 20, '用户名长度应在 2~20 个字符之间')
]

表单中用法:

js 复制代码
// 直接复用
import { phone, email, username } from '@/utils/validateRules'

const rules = {
  phone,
  email,
  username
}

// 或用工厂函数微调
import { required, lengthRange } from '@/utils/validateRules'

const rules = {
  nickname: [
    required('请输入昵称'),
    lengthRange(2, 10, '昵称 2~10 个字符')
  ]
}

[⬆ 返回目录](#⬆ 返回目录)

3.2 规则组合的常见模式

js 复制代码
// 组合多个规则:手机号或邮箱二选一
export const phoneOrEmail = [
  required('请输入手机号或邮箱'),
  {
    validator: (rule, value, callback) => {
      const phoneReg = /^1[3-9]\d{9}$/
      const emailReg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
      if (phoneReg.test(value) || emailReg.test(value)) {
        callback()
      } else {
        callback(new Error('请输入正确的手机号或邮箱'))
      }
    },
    trigger: 'blur'
  }
]

组合时,通常用「必填 + 自定义 validator」更清晰。

[⬆ 返回目录](#⬆ 返回目录)

四、自定义校验:validator 怎么写才不乱?

4.1 validator 基本写法

自定义校验用 validator,需要调用 callback 告知结果:

js 复制代码
{
  validator: (rule, value, callback) => {
    // 通过:callback()
    // 失败:callback(new Error('错误提示'))
  },
  trigger: 'blur'
}

注意:必须调用 callback,否则校验会一直挂起。

[⬆ 返回目录](#⬆ 返回目录)

4.2 完整示例:密码强度校验

js 复制代码
/**
 * 密码强度校验:至少包含数字、字母,长度 8-20
 */
export const strongPassword = [
  required('请输入密码'),
  {
    validator: (rule, value, callback) => {
      if (!value) {
        callback(new Error('请输入密码'))
        return
      }
      if (value.length < 8 || value.length > 20) {
        callback(new Error('密码长度应为 8~20 位'))
        return
      }
      const hasNumber = /\d/.test(value)
      const hasLetter = /[a-zA-Z]/.test(value)
      if (!hasNumber || !hasLetter) {
        callback(new Error('密码需同时包含数字和字母'))
        return
      }
      callback()
    },
    trigger: 'blur'
  }
]

要点:每个分支都要 return,确保只调用一次 callback

[⬆ 返回目录](#⬆ 返回目录)

4.3 确认密码(联动校验)

js 复制代码
/**
 * 确认密码:依赖原密码字段
 * 使用时需在 form 上提供 getFieldValue
 */
export const confirmPassword = (passwordField = 'password') => [
  required('请再次输入密码'),
  {
    validator: (rule, value, callback) => {
      // 这里通过 rule 拿不到 form,需要在组件里用 ref 或传 form
      // 见下方组件示例
      callback()
    },
    trigger: 'blur'
  }
]

组件里需要拿到当前表单实例:

html 复制代码
<template>
  <el-form ref="formRef" :model="form" :rules="rules">
    <el-form-item label="密码" prop="password">
      <el-input v-model="form.password" type="password" />
    </el-form-item>
    <el-form-item label="确认密码" prop="confirmPassword">
      <el-input v-model="form.confirmPassword" type="password" />
    </el-form-item>
  </el-form>
</template>

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

const formRef = ref()
const form = reactive({
  password: '',
  confirmPassword: ''
})

const validateConfirmPassword = (rule, value, callback) => {
  if (value !== form.password) {
    callback(new Error('两次输入的密码不一致'))
  } else {
    callback()
  }
}

const rules = {
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 8, max: 20, message: '密码长度 8~20 位', trigger: 'blur' }
  ],
  confirmPassword: [
    { required: true, message: '请再次输入密码', trigger: 'blur' },
    { validator: validateConfirmPassword, trigger: 'blur' }
  ]
}
</script>

联动校验建议 :需要依赖其他字段时,把 validator 写在组件内,用 formformRef 取另一字段的值。

[⬆ 返回目录](#⬆ 返回目录)

五、异步校验:手机号/用户名是否已存在

5.1 写法要点

  • validator 里调用接口
  • callback 处理成功/失败
  • 防止重复请求,可加防抖
js 复制代码
/**
 * 异步校验:手机号是否已被注册
 * @param {Function} checkApi 检查接口,如 (phone) => api.checkPhone(phone)
 */
export const phoneUnique = (checkApi) => [
  required('请输入手机号'),
  pattern(/^1[3-9]\d{9}$/, '请输入正确的手机号'),
  {
    validator: (rule, value, callback) => {
      if (!value) {
        callback()
        return
      }
      checkApi(value)
        .then((res) => {
          // 假设 res.data.exists 表示已存在
          if (res.data?.exists) {
            callback(new Error('该手机号已被注册'))
          } else {
            callback()
          }
        })
        .catch(() => {
          callback(new Error('校验失败,请稍后重试'))
        })
    },
    trigger: 'blur'
  }
]

使用:

js 复制代码
import { phoneUnique } from '@/utils/validateRules'
import { checkPhoneExists } from '@/api/user'

const rules = {
  phone: phoneUnique(checkPhoneExists)
}

[⬆ 返回目录](#⬆ 返回目录)

5.2 踩坑:不要在 validator 里用 async/await 不处理 callback

错误示例:

js 复制代码
// ❌ 错误:validator 用了 async,但 callback 和 Promise 混用
{
  async validator(rule, value, callback) {
    const res = await checkApi(value)
    if (res.data.exists) {
      callback(new Error('已存在'))
    } else {
      callback()
    }
  }
}

问题:async 会让函数返回 Promise,Element Plus 的校验内部仍依赖 callback,可能造成重复调用或行为异常。

正确做法:要么全程用 callback,要么用 Promise 并确保与表单库兼容。保守起见,推荐上面的 .then/.catch 写法。

[⬆ 返回目录](#⬆ 返回目录)

六、提示语统一:避免「有的请输入、有的不能为空」

6.1 统一文案常量

js 复制代码
// src/constants/validateMessages.js

/**
 * 表单校验提示语统一管理
 * 规范:动词开头(请输入、请选择),简洁明确
 */
export const MESSAGES = {
  // 必填类
  required: {
    input: (field) => `请输入${field}`,
    select: (field) => `请选择${field}`,
    upload: (field) => `请上传${field}`
  },

  // 格式类
  format: {
    phone: '请输入正确的手机号',
    email: '请输入正确的邮箱地址',
    idCard: '请输入正确的身份证号'
  },

  // 长度类
  length: {
    range: (min, max) => `长度应在 ${min}~${max} 个字符之间`,
    min: (min) => `至少 ${min} 个字符`,
    max: (max) => `最多 ${max} 个字符`
  }
}

[⬆ 返回目录](#⬆ 返回目录)

6.2 在规则中引用

js 复制代码
// src/utils/validateRules.js
import { MESSAGES } from '@/constants/validateMessages'

const { required, format, length } = MESSAGES

export const phone = [
  { required: true, message: required.input('手机号'), trigger: 'blur' },
  { pattern: /^1[3-9]\d{9}$/, message: format.phone, trigger: 'blur' }
]

export const email = [
  { required: true, message: required.input('邮箱'), trigger: 'blur' },
  { pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, message: format.email, trigger: 'blur' }
]

export const username = [
  { required: true, message: required.input('用户名'), trigger: 'blur' },
  { min: 2, max: 20, message: length.range(2, 20), trigger: 'blur' }
]

好处:改文案只需改 MESSAGES,全项目一致。

[⬆ 返回目录](#⬆ 返回目录)

七、完整目录与文件结构示例

复制代码
src/
├── constants/
│   └── validateMessages.js    # 提示语文案
├── utils/
│   └── validateRules.js       # 校验规则(含工厂函数、预定义规则)
└── views/
    └── user/
        └── Register.vue       # 使用校验的表单页面

validateRules.js 可按模块拆分,例如:

js 复制代码
// validateRules/index.js - 统一导出
export * from './common'      // 必填、长度、正则
export * from './user'        // 用户相关:用户名、手机、邮箱、密码
export * from './business'    // 业务相关

[⬆ 返回目录](#⬆ 返回目录)

八、常见踩坑与解决

坑 1:校验不触发

原因el-form-itempropform 对象字段名不一致。

html 复制代码
<!-- ❌ prop 和 form 字段对不上 -->
<el-form :model="form">
  <el-form-item prop="userName">  <!-- form 里是 username -->
    <el-input v-model="form.username" />
  </el-form-item>
</el-form>

解决:prop 必须和 form 的 key 完全一致。

坑 2:动态表单项校验异常

动态增删表单项时,prop 建议用完整路径,如 list.0.phonelist.1.phone,并在 el-form 上设置 :rules 时,对动态项使用嵌套规则。

坑 3:提交时手动触发表单校验

js 复制代码
const formRef = ref()

const onSubmit = async () => {
  const valid = await formRef.value?.validate()
  if (!valid) return
  // 校验通过,继续提交
}

注意:validate() 返回的是 Promise,校验失败会 reject,成功时 resolve,可用 try/catchvalidate().catch(() => {}) 处理。

坑 4:清空校验结果

重置或关闭弹窗时,需要清空校验状态:

js 复制代码
formRef.value?.resetFields()           // 重置表单 + 清除校验
formRef.value?.clearValidate()         // 只清除校验,不重置值
formRef.value?.clearValidate('phone')  // 只清除某个字段

[⬆ 返回目录](#⬆ 返回目录)

九、小结

建议
规则复用 抽到独立文件,用工厂函数生成规则,避免复制粘贴
自定义校验 需要依赖其他字段时,validator 写在组件内;分支里都要 callback
异步校验 .then/.catch 配合 callback,避免 async 与 callback 混用
提示语 用常量集中管理,动词开头、表述统一
目录结构 constants/validateMessages.js + utils/validateRules.js 分层

表单校验本身不难,难在长期维护时还能保持清晰、统一。按上面的规范做,可以从一开始就减少「规则散落、文案混乱、难以排查」的问题。

[⬆ 返回目录](#⬆ 返回目录)

🔍 系列模块导航

📝 编码语法规范

一、《Vue3 + Element Plus 表单开发实战:防重复提交、校验、重置、loading 统一|表单与表格规范篇》

二、《Vue3 + Element Plus 表单校验实战:规则复用、自定义校验、提示语统一,告别混乱避坑|表单与表格规范篇》
三、《Vue3 + Element Plus 表格查询规范:条件管理、分页联动 + 避坑,标准化写法|表单与表格规范篇》
四、《Vue3 + Element Plus 表格实战:批量操作、行内编辑、跨页选中逻辑统一|表单与表格规范篇》
五、《VXE-Table 4.x 实战规范:列配置 + 合并单元格 + 虚拟滚动,避坑卡顿 / 错乱 / 合并失效|表单与表格规范篇》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~

📚 系列总览

前端规范实战系列 」正在持续更新中,后续会整理一篇《前端规范实战系列全系列目录导航》,包含每篇文章简介 + 直达链接,方便大家按顺序、体系化学习。

更新中,敬请期待~

[⬆ 返回目录](#⬆ 返回目录)


技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护

哪怕每次只吃透一条规范,长期下来,差距会非常明显。

后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。

觉得有用欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。

我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~

相关推荐
Cobyte1 小时前
来,实现一个 Mini Claude Code:从底层理解 AI Agent
前端·aigc·ai编程
2401_894241921 小时前
基于C++的数据库连接池
开发语言·c++·算法
阿贵---1 小时前
C++中的适配器模式
开发语言·c++·算法
C羊驼1 小时前
C语言学习笔记(十二):动态内存管理
c语言·开发语言·经验分享·笔记·青少年编程
酉鬼女又兒2 小时前
零基础快速入门前端JavaScript 浏览器环境输入输出语句全解析:从弹框交互到控制台调试(可用于备赛蓝桥杯Web应用开发赛道)
前端·javascript·职场和发展·蓝桥杯·js
清汤饺子2 小时前
搞懂 Cursor 后,我一行代码都不敲了《实战篇》
前端·javascript·后端
SuperEugene2 小时前
Vue3 + Element Plus 表格查询规范:条件管理、分页联动 + 避坑,标准化写法|表单与表格规范篇
开发语言·前端·javascript·vue.js·前端框架
小邓睡不饱耶2 小时前
东方财富网股票数据爬取实战:从接口分析到数据存储
开发语言·爬虫·python·网络爬虫
dapeng28702 小时前
C++与Docker集成开发
开发语言·c++·算法