美文网首页前端编程学习笔记
基于 schema 的数据校验

基于 schema 的数据校验

作者: NARUTO_86 | 来源:发表于2020-01-17 16:23 被阅读0次

    前端开发中,对要提交的表单数据进行校验是很常见的需求,有开源的基于框架的数据校验库,也有组件库内置的校验功能,这里介绍的是一种脱离框架、组件的独立数据校验思路。

    我们团队的 Vue 项目比较多,先看下这一块的数据校验方案:

    1. vuelidate:https://github.com/vuelidate/vuelidate
    2. 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' }
            ]
        }
      }
    }
    

    看起来差不多,但是由于组件的支持,使用起来比较方便,只进行一次绑定就好:

    <el-form :model="form" :rules="rules">
      <el-form-item label="姓名" prop="name">
        <el-input v-model="form.name" />
      </el-form-item>
      <el-form-item label="年龄" prop="age">
        <el-input v-model="form.age" type="number" />
      </el-form-item>
    </el-form>
    

    用户输入错误时,组件可以直接展示错误文案,另外表单组件上还定义了 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 之间' }
    // }
    

    怎么样,是不是稍微清爽了一些。

    来看一个真实业务案例:

    说明:以下示例代码不涉及业务机密,校验规则来源于公开的央行反洗钱上报数据规范。

    代码示例

    图中的 BankNameBankAccountPlaceholderIP 都是已经定义好的校验类型,通过 .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 数据呢?

    相关文章

      网友评论

        本文标题:基于 schema 的数据校验

        本文链接:https://www.haomeiwen.com/subject/mhzczctx.html