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

📑 文章目录
- 一、开篇:为什么需要「表单校验规范」?
- [二、Vue3 + Element Plus 表单校验基础扫盲](#二、Vue3 + Element Plus 表单校验基础扫盲)
- [2.1 表单校验的两种常见写法](#2.1 表单校验的两种常见写法)
- [2.2 trigger:什么时候触发校验?](#2.2 trigger:什么时候触发校验?)
- 三、规则复用:如何设计可复用的校验规则?
- [3.1 规则工厂函数](#3.1 规则工厂函数)
- [3.2 规则组合的常见模式](#3.2 规则组合的常见模式)
- [四、自定义校验:validator 怎么写才不乱?](#四、自定义校验:validator 怎么写才不乱?)
- [4.1 validator 基本写法](#4.1 validator 基本写法)
- [4.2 完整示例:密码强度校验](#4.2 完整示例:密码强度校验)
- [4.3 确认密码(联动校验)](#4.3 确认密码(联动校验))
- 五、异步校验:手机号/用户名是否已存在
- [5.1 写法要点](#5.1 写法要点)
- [5.2 踩坑:不要在 validator 里用 async/await 不处理 callback](#5.2 踩坑:不要在 validator 里用 async/await 不处理 callback)
- 六、提示语统一:避免「有的请输入、有的不能为空」
- [6.1 统一文案常量](#6.1 统一文案常量)
- [6.2 在规则中引用](#6.2 在规则中引用)
- 七、完整目录与文件结构示例
- 八、常见踩坑与解决
- 九、小结
- [🔍 系列模块导航](#🔍 系列模块导航)
同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。
(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)
很多前端开发者都会遇到一个瓶颈:
代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。
想写出干净、优雅、可维护 的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验。
这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。
帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。
一、开篇:为什么需要「表单校验规范」?
在 Vue 项目里,表单校验几乎是每个页面都会遇到的事。不少人会用 el-form、a-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 写在组件内,用 form 或 formRef 取另一字段的值。
[⬆ 返回目录](#⬆ 返回目录)
五、异步校验:手机号/用户名是否已存在
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-item 的 prop 与 form 对象字段名不一致。
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.phone、list.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/catch 或 validate().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,与你一起写规范、写优质代码,我们下篇干货见~