基于 schema 的數(shù)據(jù)校驗

前端開發(fā)中,對要提交的表單數(shù)據(jù)進行校驗是很常見的需求,有開源的基于框架的數(shù)據(jù)校驗庫,也有組件庫內(nèi)置的校驗功能,這里介紹的是一種脫離框架、組件的獨立數(shù)據(jù)校驗思路。

我們團隊的 Vue 項目比較多,先看下這一塊的數(shù)據(jù)校驗方案:

  1. vuelidate:https://github.com/vuelidate/vuelidate
  2. Element UI:https://element.eleme.io/2.8/#/zh-CN/component/form

vuelidate

vuelidate 是基于 Vue 的數(shù)據(jù)校驗庫,特點是根據(jù)定義的校驗規(guī)則,在數(shù)據(jù)變更時自動校驗,利用了 Vue 數(shù)據(jù)響應(yīng)式機制:

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)
     }
   }
}

實現(xiàn)要引入 vuelidate 到 Vue,從而通過 validations 聲明的數(shù)據(jù)校驗規(guī)則,在實例初始化后,會生成對應(yīng)的 $v 數(shù)據(jù),記錄內(nèi)部各項校驗的結(jié)果:

$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 組件上展示校驗錯誤文案,還是在表單提交時獲取校驗結(jié)果,都是通過訪問 $v 實現(xiàn)。

Element UI

作為組件庫,Element UI 的數(shù)據(jù)校驗與表單組件直接關(guān)聯(lián),也是先定義校驗規(guī)則:

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() 方法,可以在提交時手動調(diào)用進行數(shù)據(jù)校驗。

上面介紹的兩種數(shù)據(jù)校驗方案,都可以滿足日常表單校驗需求。不過兩者都有一個問題,依賴其他框架、庫。這使得其應(yīng)用場景受限,顯然在 Node 應(yīng)用中就不方便使用。也可以認(rèn)為,作為數(shù)據(jù)校驗方案,兩者都不夠是“純粹”。

下面介紹一個比較“純粹”的方案:

schema-typed

項目地址:https://github.com/rsuite/schema-typed

schema-typed 首先為需要校驗的數(shù)據(jù)創(chuàng)建一個模型:

import { SchemaModel, StringType, NumberType } from 'schema-typed'

const model = SchemaModel({
  name: StringType().isRequired('姓名不能為空'),
  age: NumberType().range(18, 30, '年齡應(yīng)在18-30之間')
})

然后使用模型來校驗數(shù)據(jù):

model.check({
  name: 'foo',
  age: 40
})

// 結(jié)果:
// {
//   name: { hasError: false },
//   age: { hasError: true, errorMessage: '年齡應(yīng)在18-30之間' }
// }

好像也沒啥了不起。不過既然是數(shù)據(jù)的 shema,對于復(fù)雜數(shù)據(jù)結(jié)構(gòu)也是有支持的,例如:

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 組件工具集的其中一個工具,但是顯然,它可以直接應(yīng)用到 Vue 項目甚至 Node 項目中。

并且,這種定義數(shù)據(jù) schema,基于 schema 對數(shù)據(jù)校驗的方式,顯然對于業(yè)務(wù)代碼的拆分也很有幫助。

寫到這里,介紹了幾種數(shù)據(jù)校驗方案好像也就差不多了。不過我還要再額外介紹一下自己基于 shema-typed 改進的數(shù)據(jù)校驗庫:

schema-validate

項目地址:https://github.com/luobotang/schema-validate

先說下我認(rèn)為 schema-typed 不太好的一些細節(jié):

  • StringType()、NumberType() 這樣的名字太啰嗦了,寫出來的 shema 不夠簡潔易讀
  • NumberType() 內(nèi)部其實是兼容 '123' 這樣的類似數(shù)值的字符串的,不太“嚴(yán)謹(jǐn)”
  • 每個規(guī)則的錯誤文案都需要單獨指定,不然缺省錯誤文案是沒法用的(還是英文的)
  • 不支持一個屬性字段對應(yīng)多種類型的情況
  • 多數(shù)規(guī)則方法名稱也太啰嗦

先看結(jié)果吧,經(jīng)過改造之后,之前 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
})

// 結(jié)果:
// {
//   name: { hasError: false },
//   age: { hasError: true, errorMessage: '年齡應(yīng)在 18 到 30 之間' }
// }

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

來看一個真實業(yè)務(wù)案例:

說明:以下示例代碼不涉及業(yè)務(wù)機密,校驗規(guī)則來源于公開的央行反洗錢上報數(shù)據(jù)規(guī)范。

代碼示例

圖中的 BankName、BankAccount、PlaceholderIP 都是已經(jīng)定義好的校驗類型,通過 .clone() 重新指定數(shù)據(jù)描述(以便在錯誤文案中提現(xiàn)數(shù)據(jù)字段信息)或直接復(fù)用。

圖中的 T.any() 就是支持字段數(shù)據(jù)多類型的機制,匹配任一內(nèi)部類型都視為校驗通過。

此外,如果按照原來 schema-typed 的方式,通過 model.check(data) 返回的是一個復(fù)雜的對象,包含各個字段的校驗結(jié)果,對于想直接獲得一個驗證結(jié)果的情況,就需要自己遍歷結(jié)果去查找了。為此,在 schema-validate 中新增了一個 model.validate(data) 方法:

mode.validate(data)

// 結(jié)果:
// {
//   hasError: true,
//   errorMessage: 'xxx'
// }

并且在內(nèi)部執(zhí)行中,遇到第一個校驗錯誤就會直接返回,不再執(zhí)行其他字段的校驗。

總結(jié)

通過 schema 的方式來“聲明”數(shù)據(jù)結(jié)構(gòu),并用于數(shù)據(jù)校驗,在我看來是比較“清爽”的方式。當(dāng)然,相比 vuelidate 和 Element UI 來說,需要開發(fā)者做一些額外工作,但這在我看來反倒是優(yōu)勢。這是這些沉淀下來的業(yè)務(wù)數(shù)據(jù)的 schema,是不會隨著技術(shù)棧的更新而被迫更新,并且可以在多個不同技術(shù)棧的項目中復(fù)用。

額外暢想一下,這些數(shù)據(jù) schema,是不是也可以用于生成 mock 數(shù)據(jù)呢?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容