物件校驗器
本章所有程式碼,均在github.com/antgod/func…
我們來解決一個js的普遍需求。js誕生時,js的作用僅僅一個前端做表單檢查的校驗器。如今,各種校驗框架早已成熟,但是我們仍然有大量的需求需要校驗物件。比如後端返回的json我們需要校驗,前端提交的json我們需要校驗。資料管理中的store我們需要校驗,或者說,任意一個函式,我們都需要對入參進行校驗。
很多框架或者函式,直接把校驗寫在寫在函式的最頂端。或許有的時候,我們會抽出一個公共函式來校驗,比如這樣:
const grund = (checker, handle, errorCallback = args => args) => (...args) => {
const result = checker(...args)
if (result.length) {
errorCallback(result)
} else {
return handle(...args)
}
}
const handle = (...args) => {
console.log('正確處理', args)
return '處理完畢'
}
const checker = (...args) => !args.length ? ['該函式的引數不能為空'] : []
grund(checker, handle, errors => console.log(errors))()複製程式碼
grund函式用來檢驗傳入引數是否正確,如果正確,執行正確的處理邏輯,如果錯誤,執行異常處理邏輯。
由於checker過於簡單,這並不能滿足我們的需求。有時候,我們經常要根據多組條件校驗,這就需要多個校驗器。而把checker當做陣列傳入grund顯然不是一個好方法。我們需要在checker函式中就傳入多組校驗器,而checker的工作就是負責把所有校驗器合成執行,返回執行結果即可。
const checker = (...validators) => (...args) => {
return validators.reduce((errors, validator) => {
return validator(...args) ? errors : [...errors, validator.message]
}, [])
}複製程式碼
注意這裡我們使用了每個校驗器的message屬性,這似乎是潛規則
,沒錯,這類似於前後端介面的約定欄位success,message
。幾乎所有介面都叫這兩個欄位(即使不同,也大同小異)。我們呼叫時候,需要給每個校驗器加上message欄位。
const validator1 = () => false
validator1.message = '校驗1不通過'
const validator2 = () => false
validator2.message = '校驗2不通過'
checker(validator1, validator2)({})複製程式碼
好了,效果達成了。可是每個vdalidator都要設定message讓人感到痛苦。如果能夠自動新增message豈不是更好?我們可以用一個API建立校驗器,讓人一目瞭然。
const validator = (handle, message) => {
const fun = (...args) => handle(...args)
fun.message = message
return fun
}複製程式碼
API類似於redux的actionCreator。能讓我們輕鬆建立vilidator。使用時也非常簡單。
const v1 = (object) => {
return object.a === 1
}
const v2 = (object) => {
return object.b === 2
}
const v3 = (object) => {
return object.c === true
}
// test
const c1 = checker(validator(v1, 'error1'), validator(v2, 'error2'), validator(v3, 'error3'))複製程式碼
我們有三個校驗規則,v1,v2,v3,通過validator建立了三個校驗器傳入checker,返回了檢查器c1。程式碼非常清晰明瞭。
這時候我們開始驗證。
const object = {
a: 1,
b: 2,
c: false,
}
const handle = (...args) => {
console.log('繼續處理', args)
return '返回正確'
}
grund(c1, handle, errors => console.log(errors))(object)
// => [ 'error3' ]複製程式碼
v3要求c屬性必須是true,但是測試資料的c屬性是false。好,檢查器與校驗器都能正常工作。可我們如果有這樣的需求,需要object裡面必須有a,b,c,d四個key,怎麼辦呢?
新增簡單的validator當然沒有問題。然而,保持高水平的編碼規則需要一些有趣的技巧。高階函式的本質就是引數可以作為返回函式閉包的行為配置,牢記這一點,可以讓你隨時隨地返回需要函式的地方返回配置過的閉包。
以上面的需求為例。我們再建立一個所需鍵的簡單列表會更加流暢。為了讓這個建立列表的函式符合約定,我們讓他返回一個閉包和一個錯誤。
const hasKeys = (...keys) => {
const fun = obj => keys.every(key => obj[key] !== undefined)
fun.message = `object must have value of keys: ${keys}`
return fun
}複製程式碼
你會發現閉包用來檢查給的物件是否有效。hasKeys的目的是為了向fun函式提供執行配置。此外,通過直接返回一個函式名,我們很好的描述了需求。從一個函式返回另一個函式的技術,這個過程中捕獲引數-被稱為柯里化。
使用示例如下:
const c1 = checker(validator(v1, 'error1'), validator(v2, 'error2'), validator(v3, 'error3'), hasKeys('a', 'b', 'c', 'd'))
const object = {
a: 1,
b: 2,
c: true,
}
const handle = (...args) => {
console.log('繼續處理', args)
return '返回正確'
}
grund(c1, handle, errors => console.log(errors))(object)
// => [ 'object must have value of keys: a,b,c,d' ]複製程式碼
當然,我們也可以直接執行c1函式。
const c1 = checker(validator(v1, 'error1'), validator(v2, 'error2'), validator(v3, 'error3'), hasKeys('a', 'b', 'c', 'd'))
const object1 = {
a: 1,
b: 2,
}
const object2 = {
a: 1,
b: 2,
c: true,
d: 1,
}
console.log(c1(object1))
// => [ 'error3', 'object must have value of keys: a,b,c,d' ]
console.log(c1(object2))
// => []複製程式碼
在任何情況下,使用c1檢查器構建語法一致性
,也就是說,在校驗器固定的情況下,檢查器會按照校驗器所定義的方式校驗,我們只需要傳入需要校驗的引數即可。