对象校验器
本章所有代码,均在
我们来解决一个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 = () => falsevalidator1.message = '校验1不通过'const validator2 = () => falsevalidator2.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}// testconst 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检查器构建语法一致性
,也就是说,在校验器固定的情况下,检查器会按照校验器所定义的方式校验,我们只需要传入需要校验的参数即可。