先附上專案的連結地址:
github: github.com/moohng/vali…
動機
有表單的地方必有校驗,我們使用不同的框架有不同的校驗方法。在PC端基於React
的Ant Design
框架中的Form
表單的功能就十分強大,而在移動端,卻很難找到一款能與之匹敵完整框架。移動端比較流行的vux
框架,雖然有的元件自帶了校驗功能,但然並卵,基本上很難匹配真實專案的需求。
放棄vux
元件自帶的校驗功能,後來在github
上找到一款比較流行的校驗庫vee-validate
。這個庫做到了與表單元件之間的解耦,確實也能夠靈活的實現各式各樣的專案需求,但給我的感覺就是有點重。一般來說,移動端的開銷越低越好,程式碼越輕量越好,區區一個校驗,實在不忍心用上如此龐大的工具。
思前想後,還不如自己實現一套校驗方案。首先校驗工具必須要與元件完全解耦,其次校驗工具要足夠輕量,夠用就好。移動端不比PC端,考慮到效能,我們一般校驗表單都是在提交的時候進行校驗,對不滿足要求的欄位進行提示(比如:欄位元件樣式變紅,Toast
輕提示等)。
思考
-
為什麼要與元件解耦?
很多時候,我們的表單元件都並非真正意義上的表單,尤其是現在
React
和Vue
帶來的元件化時代,很多表單元件都是根據我們自己的需求來封裝的,各式各樣。如果每封裝一個元件,就要帶上完整的校驗功能,那必然是痛苦的,而且有時候不同的第三方元件庫很難實現校驗的統一性。所以,我們校驗的應該是欄位,跟元件本身沒有任何關係,儘管我們的欄位取值是來自於元件。如此一來,我們的校驗工具就適用於任何元件,不管它是否是真正意義上的表單,只要將這個元件跟校驗的欄位對應起來即可。所以,我們其實是對欄位的校驗。 -
如何更輕量的實現對欄位的校驗?
理想的方式應該是這樣的:校驗結果 = 校驗函式(校驗目標集合, 校驗規則),用程式碼實現是這樣的:
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)
思路:
- 遍歷規則集合,拿每一條規則去校驗目標集合中對應的欄位;
- 首先是為空校驗,空的定義應該是:
null
、undefined
、[]
、{}
、''
等; - 若為空校驗不通過,記錄提示文字資訊並跳過其他校驗,否則繼續下一個校驗;
- 然後是正則校驗,同理;
- 最後是自定義校驗,自定義校驗函式應該傳入當前校驗的值和整個目標的集合物件(可能會存在與其他欄位作比較等)。
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])
:校驗結果中第一個欄位的key
。index
使用同上。
應用
該校驗庫已經發布到npm倉庫,可通過npm
或yarn
工具進行下載。
$ 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
物件去自行擴充套件。
最後
如果覺得不錯,請大家多多支援~