超輕量代碼實(shí)現(xiàn)字段校驗(yàn)工具庫(kù)(移動(dòng)端)

先附上項(xiàng)目的鏈接地址:

github: https://github.com/moohng/validator

動(dòng)機(jī)

有表單的地方必有校驗(yàn)逗栽,我們使用不同的框架有不同的校驗(yàn)方法择诈。在PC端基于ReactAnt Design框架中的Form表單的功能就十分強(qiáng)大,而在移動(dòng)端,卻很難找到一款能與之匹敵完整框架。移動(dòng)端比較流行的vux框架,雖然有的組件自帶了校驗(yàn)功能成玫,但然并卵,基本上很難匹配真實(shí)項(xiàng)目的需求拳喻。

放棄vux組件自帶的校驗(yàn)功能哭当,后來(lái)在github上找到一款比較流行的校驗(yàn)庫(kù)vee-validate。這個(gè)庫(kù)做到了與表單組件之間的解耦冗澈,確實(shí)也能夠靈活的實(shí)現(xiàn)各式各樣的項(xiàng)目需求钦勘,但給我的感覺(jué)就是有點(diǎn)重。一般來(lái)說(shuō)亚亲,移動(dòng)端的開銷越低越好彻采,代碼越輕量越好,區(qū)區(qū)一個(gè)校驗(yàn)捌归,實(shí)在不忍心用上如此龐大的工具肛响。

思前想后,還不如自己實(shí)現(xiàn)一套校驗(yàn)方案惜索。首先校驗(yàn)工具必須要與組件完全解耦特笋,其次校驗(yàn)工具要足夠輕量,夠用就好巾兆。移動(dòng)端不比PC端猎物,考慮到性能,我們一般校驗(yàn)表單都是在提交的時(shí)候進(jìn)行校驗(yàn)角塑,對(duì)不滿足要求的字段進(jìn)行提示(比如:字段組件樣式變紅霸奕,Toast輕提示等)。

思考

  • 為什么要與組件解耦吉拳?

    很多時(shí)候,我們的表單組件都并非真正意義上的表單适揉,尤其是現(xiàn)在ReactVue帶來(lái)的組件化時(shí)代留攒,很多表單組件都是根據(jù)我們自己的需求來(lái)封裝的煤惩,各式各樣。如果每封裝一個(gè)組件炼邀,就要帶上完整的校驗(yàn)功能魄揉,那必然是痛苦的,而且有時(shí)候不同的第三方組件庫(kù)很難實(shí)現(xiàn)校驗(yàn)的統(tǒng)一性拭宁。所以洛退,我們校驗(yàn)的應(yīng)該是字段,跟組件本身沒(méi)有任何關(guān)系杰标,盡管我們的字段取值是來(lái)自于組件兵怯。如此一來(lái),我們的校驗(yàn)工具就適用于任何組件腔剂,不管它是否是真正意義上的表單媒区,只要將這個(gè)組件跟校驗(yàn)的字段對(duì)應(yīng)起來(lái)即可。所以掸犬,我們其實(shí)是對(duì)字段的校驗(yàn)袜漩。

  • 如何更輕量的實(shí)現(xiàn)對(duì)字段的校驗(yàn)?

    理想的方式應(yīng)該是這樣的:校驗(yàn)結(jié)果 = 校驗(yàn)函數(shù)(校驗(yàn)?zāi)繕?biāo)集合, 校驗(yàn)規(guī)則)湾碎,用代碼實(shí)現(xiàn)是這樣的:result = validator(target, rules);宙攻。我們只要在提交表單的時(shí)候執(zhí)行校驗(yàn)函數(shù),傳入待校驗(yàn)的字段集合和一套校驗(yàn)規(guī)則介褥,拿到最后的校驗(yàn)結(jié)果座掘。最后,根據(jù)結(jié)果來(lái)做一些后續(xù)的處理呻顽。

  • 目標(biāo)集合雹顺、校驗(yàn)規(guī)則和校驗(yàn)結(jié)果如何定義?

    一個(gè)表單存在多個(gè)字段廊遍,目標(biāo)集合應(yīng)該就是一個(gè)包含所有字段的一個(gè)對(duì)象嬉愧。

    { name: '小明', age: 16, email: 'xiaoming@qq.com' }
    

    不同字段往往有不一樣的校驗(yàn)規(guī)則,校驗(yàn)規(guī)則需要對(duì)每一個(gè)字段進(jìn)行定義喉前,因而也應(yīng)該是一個(gè)包含所有字段的一個(gè)對(duì)象没酣。

    { name: 規(guī)則1, age: 規(guī)則2, email: 規(guī)則3 }
    

    對(duì)規(guī)則的定義,我們一般的需求有為空判斷卵迂、字符串是否滿足條件裕便、數(shù)字是否在指定范圍之內(nèi)其他特殊處理见咒〕ニィ總結(jié)起來(lái),校驗(yàn)類型可以歸為3類:為空、正則下翎、自定義缤言。為空判斷最為常見(jiàn),單獨(dú)歸為一類视事;正則表達(dá)式可以滿足大多數(shù)的校驗(yàn)規(guī)則胆萧,可歸為一類;前兩種基本上已經(jīng)滿足了百分之七八十的業(yè)務(wù)場(chǎng)景俐东,所以將剩下的所有校驗(yàn)規(guī)則通過(guò)自定義函數(shù)實(shí)現(xiàn)跌穗。所以最后每一個(gè)字段的校驗(yàn)規(guī)則是這樣的:

    {
        required: true,
        pattern: /^QQ\w{2,5}$/,
        validator() {
            // TODO...
        }
    }
    

    當(dāng)我們拿到校驗(yàn)結(jié)果,我們想要知道校驗(yàn)結(jié)果是否通過(guò)虏辫、校驗(yàn)結(jié)果中不通過(guò)的字段以及每個(gè)字段對(duì)應(yīng)的提示語(yǔ)蚌吸。所以,校驗(yàn)結(jié)果應(yīng)該是一個(gè)包含所有不通過(guò)字段的對(duì)象乒裆。

    const result = { name: '姓名不能為空', age: '年齡必須大于18歲' }
    

    對(duì)于校驗(yàn)結(jié)果套利,我們或許只關(guān)心本次校驗(yàn)是否通過(guò),僅僅拿到單純的校驗(yàn)結(jié)果對(duì)象不便于操作鹤耍。因此肉迫,result應(yīng)該還有一個(gè)hasError()函數(shù)用于返回校驗(yàn)結(jié)果是否出錯(cuò),另外還應(yīng)有一個(gè)first()函數(shù)用于返回第一個(gè)錯(cuò)誤字段的提示信息稿黄。

    result.hasError()   // true
    result.first()      // 姓名不能為空
    

    根據(jù)不同的需求喊衫,可能還應(yīng)該提供其他的操作方法。

實(shí)現(xiàn)

校驗(yàn)函數(shù)(validator)

思路:

  • 遍歷規(guī)則集合杆怕,拿每一條規(guī)則去校驗(yàn)?zāi)繕?biāo)集合中對(duì)應(yīng)的字段族购;
  • 首先是為空校驗(yàn),空的定義應(yīng)該是:null陵珍、undefined寝杖、[]{}互纯、''等瑟幕;
  • 若為空校驗(yàn)不通過(guò),記錄提示文本信息并跳過(guò)其他校驗(yàn)留潦,否則繼續(xù)下一個(gè)校驗(yàn)只盹;
  • 然后是正則校驗(yàn),同理兔院;
  • 最后是自定義校驗(yàn)殖卑,自定義校驗(yàn)函數(shù)應(yīng)該傳入當(dāng)前校驗(yàn)的值和整個(gè)目標(biāo)的集合對(duì)象(可能會(huì)存在與其他字段作比較等)。
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 = `請(qǐng)輸入正確的${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 : `請(qǐng)輸入${alias}`)
    } else if (pattern && pattern instanceof RegExp && !pattern.test(value)) { // 正則校驗(yàn)
      tips = message
    } else if (typeof validate === 'function') { // 自定義校驗(yàn)函數(shù)
      const res = validate(value, target)
      tips = typeof res === 'string' ? res : (!res ? message : null)
    }
    return tips ? { ...errors, [key]: tips } : { ...errors }
  }, {})
  return new Result(results)
}

校驗(yàn)結(jié)果(Result)

我們看到坊萝,在validator函數(shù)中孵稽,返回了Result的實(shí)例對(duì)象许起。代碼很簡(jiǎn)單:

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

其實(shí)也就是在普通的對(duì)象上,擴(kuò)展(原型上添加)了3個(gè)方法肛冶。

API

validator

核心校驗(yàn)函數(shù):validator(target: Object, rules: Object) => result: Result街氢。

target

待校驗(yàn)的目標(biāo)對(duì)象集合:{ name: 'Kevin', age: 18 }

rules

校驗(yàn)規(guī)則集合:{ name: rule1, age: rule2 }睦袖。

  • alias:字段別名。比如:姓名荣刑,年齡馅笙。作為默認(rèn)提示語(yǔ)輸出,忽略則為key厉亏。
  • trim:是否忽略字符串首尾空格董习。默認(rèn)true
  • required:是否必須爱只。為字符串時(shí)作為提示語(yǔ)輸出皿淋。
  • pattern:正則表達(dá)式。
  • message:作為校驗(yàn)提示語(yǔ)輸出恬试,忽略則輸出默認(rèn)提示語(yǔ)窝趣。
  • validate:自定義校驗(yàn)函數(shù),validate(value, target)训柴。返回字符串時(shí)作為此次校驗(yàn)不通過(guò)的提示語(yǔ)輸出哑舒,或者返回boolean表示是否通過(guò)本次校驗(yàn),返回fasle時(shí)輸出默認(rèn)提示語(yǔ)幻馁。

Result

包括所有不通過(guò)校驗(yàn)的字段集合: { name: '姓名不能為空', age: '年齡必須大于12歲' }洗鸵。

方法:

  • result.hasError():本次校驗(yàn)是否有錯(cuò)(不通過(guò))。
  • result.first([index: Number]):校驗(yàn)結(jié)果中第一個(gè)字段的提示語(yǔ)仗嗦。index指定第幾個(gè)字段膘滨,默認(rèn)為1
  • result.firstKey([index: Number]):校驗(yàn)結(jié)果中第一個(gè)字段的key稀拐。index使用同上火邓。

應(yīng)用

該校驗(yàn)庫(kù)已經(jīng)發(fā)布到npm倉(cāng)庫(kù),可通過(guò)npmyarn工具進(jìn)行下載钩蚊。

$ 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>

假如你需要在校驗(yàn)報(bào)錯(cuò)之后對(duì)相應(yīng)的組件進(jìn)行樣式上的處理贡翘,可通過(guò)響應(yīng)式的result去完成:

<template>
  <x-form>
    <x-input
      :class="{ 'error': result.name }"
      label="姓名"
      v-modal="form.name"
    />
  </x-form>
</tempalte>

更優(yōu)雅的做法是通過(guò)一個(gè)自定義指令去完成這些事情,比如一些滾動(dòng)砰逻、聚焦鸣驱、失焦、樣式切換等行為蝠咆。你需要記住的是:你已經(jīng)拿到了這個(gè)校驗(yàn)結(jié)果踊东,這個(gè)結(jié)果已包含了你需要的信息北滥,且是響應(yīng)式的(在data中已預(yù)先定義),之后的一切處理都可通過(guò)這個(gè)result對(duì)象去自行擴(kuò)展闸翅。

最后

如果覺(jué)得不錯(cuò)再芋,請(qǐng)大家多多支持~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市坚冀,隨后出現(xiàn)的幾起案子济赎,更是在濱河造成了極大的恐慌,老刑警劉巖记某,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件司训,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡液南,警方通過(guò)查閱死者的電腦和手機(jī)壳猜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)滑凉,“玉大人统扳,你說(shuō)我怎么就攤上這事〕╂ⅲ” “怎么了咒钟?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)涡匀。 經(jīng)常有香客問(wèn)我盯腌,道長(zhǎng),這世上最難降的妖魔是什么陨瘩? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任腕够,我火速辦了婚禮,結(jié)果婚禮上舌劳,老公的妹妹穿的比我還像新娘帚湘。我一直安慰自己,他們只是感情好甚淡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布大诸。 她就那樣靜靜地躺著,像睡著了一般贯卦。 火紅的嫁衣襯著肌膚如雪资柔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天撵割,我揣著相機(jī)與錄音贿堰,去河邊找鬼。 笑死啡彬,一個(gè)胖子當(dāng)著我的面吹牛羹与,可吹牛的內(nèi)容都是我干的故硅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼纵搁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吃衅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起腾誉,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤徘层,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后妄辩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惑灵,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年眼耀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佩憾。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哮伟,死狀恐怖伸头,靈堂內(nèi)的尸體忽然破棺而出煮寡,到底是詐尸還是另有隱情,我是刑警寧澤句旱,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布抡驼,位于F島的核電站鬼廓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏致盟。R本人自食惡果不足惜碎税,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望馏锡。 院中可真熱鬧雷蹂,春花似錦、人聲如沸杯道。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)党巾。三九已至萎庭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間齿拂,已是汗流浹背驳规。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留创肥,地道東北人达舒。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓值朋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親巩搏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昨登,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情贯底,實(shí)現(xiàn)同樣的效果;這時(shí)候需要使用工廠模式丰辣。簡(jiǎn)單...
    舟漁行舟閱讀 7,750評(píng)論 2 17
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法禽捆,內(nèi)部類的語(yǔ)法笙什,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法胚想,線程的語(yǔ)...
    子非魚_t_閱讀 31,623評(píng)論 18 399
  • HTML表單 在HTML中琐凭,表單是 ... 之間元素的集合,它們?cè)试S訪問(wèn)者輸入文本浊服、選擇選項(xiàng)统屈、操作對(duì)象等等,然后將...
    蘭山小亭閱讀 3,417評(píng)論 2 14
  • 二郎神開了一家出謀劃策公司牙躺。這天愁憔,來(lái)了只小猴子,骨瘦嶙峋孽拷,猴毛零落無(wú)光澤吨掌。猴子有氣無(wú)力的對(duì)二郎神說(shuō)出自己的來(lái)意:“...
    綠瑩閱讀 244評(píng)論 2 2
  • 新的一周的又開始了,現(xiàn)在到點(diǎn)叫王知遙起床已不是那么的費(fèi)勁脓恕,上周還是哼哼唧唧墨墨跡跡的膜宋,今天起來(lái)自己在那默默地穿...
    團(tuán)團(tuán)圓圓媽媽閱讀 227評(píng)論 0 1