爱客仕-前端团队博客园

vue-validator验证

前言

输入验证在前端开发中必不可少,在vue的开发过程中,vue-validator作为目前项目中在用的一个验证插件,其用法还是比较简单直观的,vue-validator API也对验证的使用做了较为详细的讲解,但是仍然有一些隐藏的点是平时注意不到的,特别是随着需求的不断变更,前端的验证也要灵活变化。

基本用法

一个最简单的表单验证大概是这样的:

1
2
3
4
<validator name="validation">
<input type="text" v-validate:name="['required']">
<p v-if="$validation.name.required">不可为空</p>
</validator>

验证器名称是由validator元素的name属性加$前缀组成,上例中验证器名称为$validation,在每次input标签的value值发生改变时触发验证,并将验证的结果保存在验证器中。$validation验证器的各个字段的属性及验证结果,如下图所示:


对于全局的各个验证字段的结果,会有相同的验证属性,其验证结果取决于所有验证字段的结果,如:

1
2
<input type="text" v-validate:name="['required']">
<input type="text" v-validate:address="{ required: true, maxlength: 10 }">

此时$validation.valid取决于$validation.name.valid && $validation.address.valid的值,只有当两者都为true时 $validation.name.valid 为 true,需要特别注意的是 全局的验证结果 是只读的,在设置全局的属性时会报错,而各自定义字段的验证结果都是可读可写的,这对于恢复验证结果、设置验证时机都是很方便的,下文会有介绍。另外一个经常使用的比较基础点是验证时机,通常验证的操作是发生在input的值改变或者失去焦点的情况下,因此对于初始化时并不需要验证,通常我们使用initial=”off”屏蔽初始化验证,但这导致了一些问题。

一个问题

一个在开发中碰到的问题,先看代码:

1
2
3
4
5
6
7
8
9
10
<validator name="validation">
<form novalidate >
<input type="text"
class="form-control"
initial="off"
v-validate:price="{ required: true, onlyNum: true"/>
<p v-if="$validation.price.required">Price is required</p>
<p v-if="!$validation.price.required && $validation.price.onlyNum">Price is number</p>
</form>
</validator>

1
2
3
4
5
6
7
8
9
10
// 局部注册onlyNum:
validators: {
onlyNum(val) {
let addTest = /^[0-9.]*$/
if (!addTest.test(val)) {
return false
}
return true
},
},

代码比较简单,简单说明下,输入的价格不能为空,必须是一个数字,并且初始时并不验证。

然后出现了比较诡异的现象,当输入任意一个字符的时候,并没有启动onlyNum的验证,当输入到两个字符的时候对onlyNum验证,如下图所示:


经验证,在required 属性搭配任意验证规则的时候都会有这种现象(当然如果不设置detect-blur=”false”,即使是第一个字符,在失去焦点时也是会触发验证的),究其原因是在initial=”off”即关闭自动验证的情况下,输入的第一个字符并不会被验证,在第二个字符或者删掉第一个字符后才会被验证,不知是vue-validator存在的bug还是设计如此,但个人认为initial=”on”是 vue-validator 完成初始编译时进行的验证,而为false 是在input 的value值进行验证,如果设计如此应该做出说明。目前采用的解决方案是:

1
2
3
<input type="text"
class="form-control"
v-validate:price="{ required: { rule: true, initial: 'off'}, onlyNum: true"/>


问题得解。

一些需求

这一部分主要介绍一些不常见的需求,由于我们项目里的新增和编辑基本都是在新的页面完成的,因此目前可能不会经常碰到下面的情况,但在迭代过程中,调整了一些交互方式,使用下拉框新增和修改,因此其验证的方式也会做一些调整,使用vue-validator时需要灵活处理,众所周知我们通过

1
2
3
4
$validate( [field], [touched], [cb] )
{String} [field] 验证的目标元素,如果不设置就验证全部
{Boolean} [touched] 设置为true时,即默认该input已经获得过焦点
{Function} [cb] 验证的回调函数

来对目标表单元素进行验证,在设置this.$validate(true)后会将所有的input的value值进行验证,得到验证结果。
现在需求是这样的,通过一个下拉组件完成列表项数的添加,其中一个input为必填项,另外一个input为非必填项,但两者都需要对输入进行验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<validator lazy name="validation">
<drop-form-group>
<label class="form-group-title"><span style="color: red">*</span>标签名称</label>
<div>
<input type="text"
id="labelName"
placeholder="必填项"
v-model="filterCache.labelName"
v-validate:label-name="{ required: { rule: true, initial: 'off' }, isComm: { rule: true, initial: 'off'} }"/>
<p v-if="$validation.labelName.required">标签名称必须填写</p>
<p v-if="!$validation.labelName.required && && $validation.labelName.isComm">标签名称为数字、字母或汉字</p>
</div>
</drop-form-group>
<drop-form-group>
<label class="form-group-title">链接地址</label>
<div class="half">
<input type="text"
placeholder="非必填项"
class="form-control"
v-model="filterCache.labelUrl"
v-validate:label-url="{ labelUri: { rule: true, initial: 'off' }}">
<p v-if="$validation.labelUrl.labelUri">请填写正确的链接地址</p>
</div>
</drop-form-group>
</validator>

由于名称是必填的,因此在提交时设置 this.$validate(‘labelName’, true),这样在提交时会对name进行必填验证,在点击取消或者关闭后,重新打开该下拉框,会发现验证的结果依然还在,这就不符合打开后恢复初始状态的需求了,这就很尴尬了。现在的需求是在点击取消或者关闭的情况下,重新打开后恢复到最原始的验证状态下,vue-validator提供了一个方法:

1
2
$resetValidation([cb])
{Function} [cb] 重置验证结果的回调

发现是好的,再进行下一项输入的验证,输入特殊字符后会出现“标签名称为数字、字母或汉字”的验证结果,此时再关闭弹出框,默认是清空掉所有输入内容的,通过打印 this.$validation.valid 发现此时为 true,即重置会将所有的都清空,如果只是想重置掉名称的验证结果而保留链接地址的验证结果呢,现在怎么做呢?前面已经说过全局的验证结果是只读属性而无法设置,因此我们设置自定义属性的值:

1
2
3
4
5
6
addCancel () {
this.filterCache = { labelUrl: '', labelName: '' }
this.$validation.labelName.required = false
this.$validation.labelName.isComm = false
this.dropDown.showDropDown = false
},

下一个需求是输入的名称不能和列表中已存在的名称重复,对于重复名称的验证其实一开始我是拒绝的,因为按照国际惯例这种重复验证都是后端同学进行操作的,按道理来说,后端同学一条sql语句就可以解决,然后给前端返回个标识就好。不过对于一些特殊场景特殊需求如果能够减少一次请求,也是一个极好的体验。我在做某个功能时就碰到了需要前端做重复验证的需求,其特殊性在于该页面已经将存在的名称全部展示在页面并且其数量也是有限的,小于等于十个,因此在前端需要做其他验证的情况下,遍历有限的名称做重复验证也未尝不可:

1
2
3
4
5
6
7
8
9
10
11
// 验证规则
isSame(val) {
if (this.vm.$data.labelList && this.vm.$data.labelList.length) {
for (let i = 0; i < this.vm.$data.labelList.length; i++) {
if (this.vm.$data.labelList[i].name === val) {
return false
}
}
}
return true
},

将 isName 添加到名称的验证项里面,labelList是页面加载时得到的数据,通过对比可以完成名称是否重复的验证。
还有一个需求,就是要处理用户的输入,如最大值要求不超过50,如果用户输入了51,则要提示“不能超过50”,并且将用户的输入强行改为5,其实这种需求是强制更改用户输入的,不知道用户体验如何,但既然产品提出来,实现看一下效果如何,其实比较简单,看代码:

1
2
3
4
5
6
7
<input type="text"
placeholder="必填项,请填整数,例如:10"
v-model="filterCache.seats"
:value="filterCache.seats"
v-validate:seats="{ required: { rule: true, initial: 'off' }, maxFifth: { rule: true, initial: 'off' } ···(验证输入是否为整数) }">
<p v-if="$validation.seats.required">座位数为必填</p>
<p v-if="!$validation.seats.required && $validation.seats.maxFifth">座位数小于50</p>

1
2
3
4
5
6
7
8
maxFifth(val) {
if (val > 50) {
setTimeout(() => {
this.vm.$data.filterCache.seats = val.slice(0, 1)
}, 2000)
}
return val < 51
},

代码中省略了一部分是否为整数的验证,在是整数并且大于50时,对其进行切割只保留第一位,设置一个定时器的原因是希望用户看到这一提示,避免蒙圈。
三个小需求都是在输入的时候处理掉,避免提交时再遍历验证。

总结

vue-validator基本能完成一些常见的验证,另外还有其它的vue验证插件如:
vue-form
暂且将在工作中遇到的不常见情况做了一些特殊处理,如果大家有更好的方式,欢迎留言。