先附上项目的链接地址:
动机
有表单的地方必有校验,我们使用不同的框架有不同的校验方法。在PC端基于React
的Ant Design
框架中的Form
表单的功能就十分强大,而在移动端,却很难找到一款能与之匹敌完整框架。移动端比较流行的vux
框架,虽然有的组件自带了校验功能,但然并卵,基本上很难匹配真实项目的需求。
放弃vux
组件自带的校验功能,后来在github
上找到一款比较流行的校验库vee-validate
。这个库做到了与表单组件之间的解耦,确实也能够灵活的实现各式各样的项目需求,但给我的感觉就是有点重。一般来说,移动端的开销越低越好,代码越轻量越好,区区一个校验,实在不忍心用上如此庞大的工具。
思前想后,还不如自己实现一套校验方案。首先校验工具必须要与组件完全解耦,其次校验工具要足够轻量,够用就好。移动端不比PC端,考虑到性能,我们一般校验表单都是在提交的时候进行校验,对不满足要求的字段进行提示(比如:字段组件样式变红,Toast
轻提示等)。
思考
-
为什么要与组件解耦?
很多时候,我们的表单组件都并非真正意义上的表单,尤其是现在
React
和Vue
带来的组件化时代,很多表单组件都是根据我们自己的需求来封装的,各式各样。如果每封装一个组件,就要带上完整的校验功能,那必然是痛苦的,而且有时候不同的第三方组件库很难实现校验的统一性。所以,我们校验的应该是字段,跟组件本身没有任何关系,尽管我们的字段取值是来自于组件。如此一来,我们的校验工具就适用于任何组件,不管它是否是真正意义上的表单,只要将这个组件跟校验的字段对应起来即可。所以,我们其实是对字段的校验。 -
如何更轻量的实现对字段的校验?
理想的方式应该是这样的:校验结果 = 校验函数(校验目标集合, 校验规则),用代码实现是这样的:
result = validator(target, rules);
。我们只要在提交表单的时候执行校验函数,传入待校验的字段集合和一套校验规则,拿到最后的校验结果。最后,根据结果来做一些后续的处理。 -
目标集合、校验规则和校验结果如何定义?
一个表单存在多个字段,目标集合应该就是一个包含所有字段的一个对象。
{ name: '小明', age: 16, email: 'xiaoming@qq.com' }
不同字段往往有不一样的校验规则,校验规则需要对每一个字段进行定义,因而也应该是一个包含所有字段的一个对象。
{ name: 规则1, age: 规则2, email: 规则3 }
对规则的定义,我们一般的需求有为空判断、字符串是否满足条件、数字是否在指定范围之内、其他特殊处理。总结起来,校验类型可以归为3类:为空、正则、自定义。为空判断最为常见,单独归为一类;正则表达式可以满足大多数的校验规则,可归为一类;前两种基本上已经满足了百分之七八十的业务场景,所以将剩下的所有校验规则通过自定义函数实现。所以最后每一个字段的校验规则是这样的:
{ required: true, pattern: /^QQ\w{2,5}$/, validator() { // TODO... } }
当我们拿到校验结果,我们想要知道校验结果是否通过、校验结果中不通过的字段以及每个字段对应的提示语。所以,校验结果应该是一个包含所有不通过字段的对象。
const result = { name: '姓名不能为空', age: '年龄必须大于18岁' }
对于校验结果,我们或许只关心本次校验是否通过,仅仅拿到单纯的校验结果对象不便于操作。因此,
result
应该还有一个hasError()
函数用于返回校验结果是否出错,另外还应有一个first()
函数用于返回第一个错误字段的提示信息。result.hasError() // true result.first() // 姓名不能为空
根据不同的需求,可能还应该提供其他的操作方法。
实现
校验函数(validator)
思路:
- 遍历规则集合,拿每一条规则去校验目标集合中对应的字段;
- 首先是为空校验,空的定义应该是:
null
、undefined
、[]
、{}
、''
等; - 若为空校验不通过,记录提示文本信息并跳过其他校验,否则继续下一个校验;
- 然后是正则校验,同理;
- 最后是自定义校验,自定义校验函数应该传入当前校验的值和整个目标的集合对象(可能会存在与其他字段作比较等)。
function validator(target, rules) {
const ruleKeys = rules ? Object.keys(rules) : []
if (!ruleKeys.length) return new Result()
const results = ruleKeys.reduce((errors, key) => {
let value = target[key]
let tips = null
const { required, pattern, validate, alias = key, message = `请输入正确的${alias}`, trim = true } = rules[key] || {}
// 去掉字符串首位空格
trim && typeof value === 'string' && (value = value.trim())
if (typeof value === undefined || value === null || !value.length || JSON.stringify(value) === '{}') {
required && (tips = typeof required === 'string' ? required : `请输入${alias}`)
} else if (pattern && pattern instanceof RegExp && !pattern.test(value)) { // 正则校验
tips = message
} else if (typeof validate === 'function') { // 自定义校验函数
const res = validate(value, target)
tips = typeof res === 'string' ? res : (!res ? message : null)
}
return tips ? { ...errors, [key]: tips } : { ...errors }
}, {})
return new Result(results)
}
校验结果(Result)
我们看到,在validator
函数中,返回了Result
的实例对象。代码很简单:
class Result {
constructor(errors = {}) {
Object.assign(this, errors)
}
hasError() {
return Object.keys(this).length > 0
}
first(index = 1) {
return Object.values(this)[index - 1]
}
firstKey(index = 1) {
return Object.keys(this)[index - 1]
}
}
其实也就是在普通的对象上,扩展(原型上添加)了3个方法。
API
validator
核心校验函数:validator(target: Object, rules: Object) => result: Result
。
target
待校验的目标对象集合:{ name: 'Kevin', age: 18 }
。
rules
校验规则集合:{ name: rule1, age: rule2 }
。
-
alias
:字段别名。比如:姓名,年龄。作为默认提示语输出,忽略则为key
。 -
trim
:是否忽略字符串首尾空格。默认true
。 -
required
:是否必须。为字符串时作为提示语输出。 -
pattern
:正则表达式。 -
message
:作为校验提示语输出,忽略则输出默认提示语。 -
validate
:自定义校验函数,validate(value, target)
。返回字符串时作为此次校验不通过的提示语输出,或者返回boolean
表示是否通过本次校验,返回fasle
时输出默认提示语。
Result
包括所有不通过校验的字段集合: { name: '姓名不能为空', age: '年龄必须大于12岁' }
。
方法:
-
result.hasError()
:本次校验是否有错(不通过)。 -
result.first([index: Number])
:校验结果中第一个字段的提示语。index
指定第几个字段,默认为1
。 -
result.firstKey([index: Number])
:校验结果中第一个字段的key
。index
使用同上。
应用
该校验库已经发布到npm仓库,可通过npm
或yarn
工具进行下载。
$ yarn add @moohng/validator
在Vue
中使用
<template>
<x-form>
<x-input label="姓名" v-modal="form.name" />
<x-upload label="头像" v-model="form.avatars" />
<x-select label="性别" v-model="form.sex" />
<x-input-number label="年龄" v-model="form.age" />
<x-button type="submit" @click="onSubmit">提交<x-button>
</x-form>
</tempalte>
<srcipt>
import { validator } from '@moohng/validator'
import rules from './rules'
export default {
data() {
return {
result: null,
form: {}
}
},
methods: {
onSubmit() {
this.result = validator(form, rules)
if (this.result.hasError()) {
this.$toast(this.result.first())
} else {
// ...
}
}
}
}
</script>
假如你需要在校验报错之后对相应的组件进行样式上的处理,可通过响应式的result
去完成:
<template>
<x-form>
<x-input
:class="{ 'error': result.name }"
label="姓名"
v-modal="form.name"
/>
</x-form>
</tempalte>
更优雅的做法是通过一个自定义指令
去完成这些事情,比如一些滚动、聚焦、失焦、样式切换等行为。你需要记住的是:你已经拿到了这个校验结果,这个结果已包含了你需要的信息,且是响应式的(在data
中已预先定义),之后的一切处理都可通过这个result
对象去自行扩展。
最后
如果觉得不错,请大家多多支持~