表单验证存在于每一个后台系统中,它承载了许多的逻辑以及状态。但是,过于某些的场景,会导致代码臃肿,如何更好地梳理它,来解决开发的痛点。
1. 前言
上篇文章介绍了,如何函数式调用表单组件从而减少维护其状态的方法基于Vue构造器创建Form组件的通用解决方案。现在来介绍下如何处理表单验证问题,现有的Vue表单验证插件有vuelidate
、vee-validate
等,但是如果场景简单的话,没有引入插件的必要,但是场景复杂的话,验证规则又要定制化的时候,应用起来没那么顺手。所以,如何调教出一个听话的Vue表单验证插件,是十分有必要的。
2. 实例以及使用规则
实例代码:GitHub - FatGe/fat-validator: fat-validator-demo
源码:GitHub - FatGe/fat-validator: fat-validator
代码中,有
<template>
<input
placeholder="请输入"
:class="['native-input', { 'error': !errors.get('nativeInput').success }]"
v-model="form.nativeInput"
v-validate:nativeInput.blur="validates.nativeInput"
@focus="errors.reset('nativeInput')"
/>
<span class="u-info">{{ errors.get('nativeInput').warn }}</span>
</template>
<script>
// mixin 验证结果validateResult
import { validateResult } from 'fat-validator'
export default {
mixins: [ validateResult],
data () {
return {
form: {
nativeInput: ''
},
validates: {
nativeInput: [
{
// 将this指针bind在当前组件内,省去传值
need: () => !!this.form.nativeInput,
warn: '不能为空'
}
]
}
}
}
}
</script>
复制代码
上述代码中存在一下几点:
-
组件的
data
中维护表单的数据form
以及待验证的规则validates
,通过() => {}
,将this
指针绑定在当前组件内,省去传值; -
v-validate:nativeInput.blur
当前input
需要校验,校验结果的key为nativeInput
,blur
代表失焦校验; -
校验规则以及警告信息则维护在组件的
data
的validates
中; -
校验结果为
errors.get('nativeInput')
,其包含{ success: true, warn }
。
3. 原理
从实例中的v-validate:nativeInput.blur
可以看出,选用了自定义指令作为连接被校验组件和校验规则之间的桥梁,主要是它两个特点:
- 钩子函数:Vue自定义指令,存在着
bind
、inserted
、update
等钩子函数,其中bind
、unbind
与元素的display息息相关,这样满足了表单挂载、注销的语义,且只触发一次; - 构造函数提供了大量的参数,可以方便与组件通信,如
el
、bind
等; 该自定义指令的注册如下。
Vue.directive('validate', {
bind (element, binding, vnode) {
// 传给参数arg => 作为校验结果中的key
// 指令绑定值value => 作为校验规则
// 修饰符modifiers => 作为出发校验的event类型
const {
arg: name,
modifiers,
value: rules
} = binding
const method = Object.keys(modifiers)[0]
},
unbind (element, binding) {
// 注册
const { arg: name, modifiers } = binding
const method = Object.keys(modifiers)[0]
}
})
复制代码
上述code解释了:
- 通过
this
指针将Form组件中data
传递给验证规则need function
; - 以及如何将rules传入指令中。
接下来介绍如何处理这些规则。为了减少代码之间的耦合,构建一个中间件作为了规则处理的处理中心,整体结构就变为下图
从组件到规则处理中心之间的通信如下export default function (Vue) {
Vue.directive('validate', {
bind (element, binding, vnode) {
//... pre code
// 获取当前组件的context
context = vnode.context
// 将eventHandler$$1绑定在当前Form的context
// 防止动态切换时无法与组件通信
if (eventHandler$$1) {
eventHandler$$1.bind(context)
} else {
eventHandler$$1 = new eventHandler(context)
}
// 如果method存在的话,当event出发时的处理函数
const handler = function () {
eventHandler$$1.broadcast(name)
}
// 在hanler中订阅一个规则处理对象
eventHandler$$1.subscribe({
name,
method,
rules,
element,
handler
})
method && on(element, method, handler)
},
unbind (element, binding) {
//... pre code
// 当Form注销时,垃圾处理
const handler = eventHandler$$1.removeSubscribe(name)
method && off(element, method, handler)
}
})
}
复制代码
当完成组件上v-validate
的解析时,就会在处理中心中订阅一个校验对象。处理中心的定义如下
export default class eventHandler {
// 构造函数
constructor (context) {
this.context = context
this.subscribers = {}
}
// 动态切换
bind (context) {
this.context = context
}
// 订阅,将其维护在eventHandler的subscribers内
subscribe (options) {
const { name } = options
this.subscribers[name] = Object.assign({}, options)
}
// 当校验事件触发时,进行校验
broadcast (name) {
const { context, subscribers } = this
const { rules } = subscribers[name]
// 校验结果
const error = findFailRule(rules)
return error.success
}
}
复制代码
需要校验时就会触发,一般的触发条件可以归类为以下两类:
- input event:blur、change、input等;
- 当点击Form的确认button时,或组件的指定event触发时。 会broadcast,通过findFailRule来获得验证失败的结果。
findFailRule = (value = {}, rules) => {
let failRule = null
if (Array.isArray(rules)) {
failRule = rules.find(item => {
return !item.need(value)
})
}
return {
warn: failRule ? failRule.warn : '',
success: !failRule
}
}
复制代码
在组件的data中,
validates: {
nativeInput: [
{
need: () => !!this.form.nativeInput,
warn: '不能为空'
}
]
}
复制代码
将验证规则定义为Array,这样方便遍历,同时也会只获取第一个报错信息。 在broadcast中获取了验证结果之后,就需要将其传递给组件。
const validateResult = {
data () {
return {
errors: {
get (param) {
return this[param]
? this[param]
: {
warn: '',
success: true
}
}
}
}
}
}
复制代码
维护一个validateResult
对象,通过mixins
注入到Form组件内,在之前,我们将eventHandler
绑定在当前context
内,这样可以获取到注入的errors
状态,当校验完成时
broadcast (name) {
const { context, subscribers } = this
const { element, rules } = subscribers[name]
const error = findFailRule(rules)
// 脏更新
context.errors[name] = error
context.$forceUpdate()
return error.success
}
复制代码
以上是一个简单的Vue表单验证插件的雏形,为了方便开发业务,还需它具备以下API:
validate
: 验证指定规则;validateAll
: 验证所有规则 => 通常用于判断Form组件是否可以可提交;reset
: 重置验证结果 => 例如input一般要求聚焦重置;resetAll
: 重置所有验证结果。 以上功能皆俱备。
为了方便引入,利用Vue.use()
将上述自定义指令封装程Vue的插件。
4. 结论
基于上述插件,可以自定义函数规则,只需要满足return
一个Boolean值即可。将业务中,常用的验证规则进行整理、封装,最后调教出一个听话的验证插件。
PS:有两点坑:
- 由于全局维护了处理中心,所以同一个Form内,name不能相同;
- 须将验证函数绑定在data内部,所以不能使用
data: () => ({})
原创声明: 该文章为原创文章,转载请注明出处。