超輕量程式碼實現欄位校驗工具庫(移動端)

紅顏漠發表於2018-02-10

先附上專案的連結地址:

github: github.com/moohng/vali…

動機

有表單的地方必有校驗,我們使用不同的框架有不同的校驗方法。在PC端基於ReactAnt Design框架中的Form表單的功能就十分強大,而在移動端,卻很難找到一款能與之匹敵完整框架。移動端比較流行的vux框架,雖然有的元件自帶了校驗功能,但然並卵,基本上很難匹配真實專案的需求。

放棄vux元件自帶的校驗功能,後來在github上找到一款比較流行的校驗庫vee-validate。這個庫做到了與表單元件之間的解耦,確實也能夠靈活的實現各式各樣的專案需求,但給我的感覺就是有點重。一般來說,移動端的開銷越低越好,程式碼越輕量越好,區區一個校驗,實在不忍心用上如此龐大的工具。

思前想後,還不如自己實現一套校驗方案。首先校驗工具必須要與元件完全解耦,其次校驗工具要足夠輕量,夠用就好。移動端不比PC端,考慮到效能,我們一般校驗表單都是在提交的時候進行校驗,對不滿足要求的欄位進行提示(比如:欄位元件樣式變紅,Toast輕提示等)。

思考

  • 為什麼要與元件解耦?

    很多時候,我們的表單元件都並非真正意義上的表單,尤其是現在ReactVue帶來的元件化時代,很多表單元件都是根據我們自己的需求來封裝的,各式各樣。如果每封裝一個元件,就要帶上完整的校驗功能,那必然是痛苦的,而且有時候不同的第三方元件庫很難實現校驗的統一性。所以,我們校驗的應該是欄位,跟元件本身沒有任何關係,儘管我們的欄位取值是來自於元件。如此一來,我們的校驗工具就適用於任何元件,不管它是否是真正意義上的表單,只要將這個元件跟校驗的欄位對應起來即可。所以,我們其實是對欄位的校驗。

  • 如何更輕量的實現對欄位的校驗?

    理想的方式應該是這樣的:校驗結果 = 校驗函式(校驗目標集合, 校驗規則),用程式碼實現是這樣的: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)

思路:

  • 遍歷規則集合,拿每一條規則去校驗目標集合中對應的欄位;
  • 首先是為空校驗,空的定義應該是:nullundefined[]{}''等;
  • 若為空校驗不通過,記錄提示文字資訊並跳過其他校驗,否則繼續下一個校驗;
  • 然後是正則校驗,同理;
  • 最後是自定義校驗,自定義校驗函式應該傳入當前校驗的值和整個目標的集合物件(可能會存在與其他欄位作比較等)。
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]):校驗結果中第一個欄位的keyindex使用同上。

應用

該校驗庫已經發布到npm倉庫,可通過npmyarn工具進行下載。

$ 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物件去自行擴充套件。

最後

如果覺得不錯,請大家多多支援~

相關文章