函數語言程式設計5-物件校驗器

瘋狂的小蘑菇發表於2017-06-26

物件校驗器

本章所有程式碼,均在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檢查器構建語法一致性,也就是說,在校驗器固定的情況下,檢查器會按照校驗器所定義的方式校驗,我們只需要傳入需要校驗的引數即可。

相關文章