基于 schema 的数据校验

前端开发中,对要提交的表单数据进行校验是很常见的需求,有开源的基于框架的数据校验库,也有组件库内置的校验功能,这里介绍的是一种脱离框架、组件的独立数据校验思路。 我们团队的 Vue 项目比较多,先看下这一块的数据校验方案: vuelidate:https://github.com/vuelidate/vuelidate Element UI:https://element.eleme.io/2.8/#/zh-CN/component/form vuelidate vuelidate 是基于 Vue 的数据校验库,特点是根据定义的校验规则,在数据变更时自动校验,利用了 Vue 数据响应式机制: import { required, minLength, between } from 'vuelidate/lib/validators' export default { data() { return { name: '', age: 0 } }, validations: { name: { required, minLength: minLength(4) }, age: { between: between(18, 30) } } } 实现要引入 vuelidate 到 Vue,从而通过 validations 声明的数据校验规则,在实例初始化后,会生成对应的 $v 数据,记录内部各项校验的结果: $v: { name: { "required": false, "minLength": false, "$invalid": true, "$dirty": false, "$error": false, "$pending": false }, age: { "between": false "$invalid": true, "$dirty": false, "$error": false, "$pending": false } } 无论是在 UI 组件上展示校验错误文案,还是在表单提交时获取校验结果,都是通过访问 $v 实现。 Element UI 作为组件库,Element UI 的数据校验与表单组件直接关联,也是先定义校验规则: export default { data() { const ageValidator = (rule, value, callback) => { if (value < 18) return callback(new Error('年龄不小于18')) if (value > 30) return callback(new Error('年龄不大于30')) callback() } return { form: { name: '', age: 0 }, rules: { name: [ { required: true, message: '请输入姓名', trigger: 'blur' }, { min: 4, max: 8, message: '长度在 4 到 8 个字符', trigger: 'blur' } ], age: [ { validator: ageValidator, trigger: 'blur' } ] } } } 看起来差不多,但是由于组件的支持,使用起来比较方便,只进行一次绑定就好: 用户输入错误时,组件可以直接展示错误文案,另外表单组件上还定义了 validate() 方法,可以在提交时手动调用进行数据校验。 上面介绍的两种数据校验方案,都可以满足日常表单校验需求。不过两者都有一个问题,依赖其他框架、库。这使得其应用场景受限,显然在 Node 应用中就不方便使用。也可以认为,作为数据校验方案,两者都不够是“纯粹”。 下面介绍一个比较“纯粹”的方案: schema-typed 项目地址:https://github.com/rsuite/schema-typed schema-typed 首先为需要校验的数据创建一个模型: import { SchemaModel, StringType, NumberType } from 'schema-typed' const model = SchemaModel({ name: StringType().isRequired('姓名不能为空'), age: NumberType().range(18, 30, '年龄应在18-30之间') }) 然后使用模型来校验数据: model.check({ name: 'foo', age: 40 }) // 结果: // { // name: { hasError: false }, // age: { hasError: true, errorMessage: '年龄应在18-30之间' } // } 好像也没啥了不起。不过既然是数据的 shema,对于复杂数据结构也是有支持的,例如: import { SchemaModel, StringType, NumberType, DateType, ArrayType, ObjectType } from 'schema-typed' const model = SchemaModel({ accountId: StringType().isRequired('账号不能为空'), trades: ArrayType().of( ObjectType().shape({ tradeId: StringType().isRequired('交易号不能为空'), tradeAmount: NumberType().min(0, '交易金额不能小于0') }) ) }) model.check({ accountId: 'foo@163.com', trades: [ {tradeId: '001', tradeAmount: 123.45}, {tradeId: '002', tradeAmount: 0.89 } ] }) schema-typed 原本是作者的 React 组件工具集的其中一个工具,但是显然,它可以直接应用到 Vue 项目甚至 Node 项目中。 并且,这种定义数据 schema,基于 schema 对数据校验的方式,显然对于业务代码的拆分也很有帮助。 写到这里,介绍了几种数据校验方案好像也就差不多了。不过我还要再额外介绍一下自己基于 shema-typed 改进的数据校验库: schema-validate 项目地址:https://github.com/luobotang/schema-validate 先说下我认为 schema-typed 不太好的一些细节: StringType()、NumberType() 这样的名字太啰嗦了,写出来的 shema 不够简洁易读 NumberType() 内部其实是兼容 '123' 这样的类似数值的字符串的,不太“严谨” 每个规则的错误文案都需要单独指定,不然缺省错误文案是没法用的(还是英文的) 不支持一个属性字段对应多种类型的情况 多数规则方法名称也太啰嗦 先看结果吧,经过改造之后,之前 schema-validate 的例子变成: import { SchemaModel, T } from '@luobotang/schema-validate' const model = SchemaModel({ name: T.string('姓名').required(), age: T.number('年龄').range(18, 30) }) model.check({ name: 'foo', age: 40 }) // 结果: // { // name: { hasError: false }, // age: { hasError: true, errorMessage: '年龄应在 18 到 30 之间' } // } 怎么样,是不是稍微清爽了一些。 来看一个真实业务案例: 说明:以下示例代码不涉及业务机密,校验规则来源于公开的央行反洗钱上报数据规范。 代码示例 图中的 BankName、BankAccount、Placeholder、IP 都是已经定义好的校验类型,通过 .clone() 重新指定数据描述(以便在错误文案中提现数据字段信息)或直接复用。 图中的 T.any() 就是支持字段数据多类型的机制,匹配任一内部类型都视为校验通过。 此外,如果按照原来 schema-typed 的方式,通过 model.check(data) 返回的是一个复杂的对象,包含各个字段的校验结果,对于想直接获得一个验证结果的情况,就需要自己遍历结果去查找了。为此,在 schema-validate 中新增了一个 model.validate(data) 方法: mode.validate(data) // 结果: // { // hasError: true, // errorMessage: 'xxx' // } 并且在内部执行中,遇到第一个校验错误就会直接返回,不再执行其他字段的校验。 总结 通过 schema 的方式来“声明”数据结构,并用于数据校验,在我看来是比较“清爽”的方式。当然,相比 vuelidate 和 Element UI 来说,需要开发者做一些额外工作,但这在我看来反倒是优势。这是这些沉淀下来的业务数据的 schema,是不会随着技术栈的更新而被迫更新,并且可以在多个不同技术栈的项目中复用。 额外畅想一下,这些数据 schema,是不是也可以用于生成 mock 数据呢?

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):