JavaScript表单验证

378 阅读6分钟

让我们来看看更优雅的表单验证吧

大家看到这段代码有何感想。

有同学会问,这有问题吗?没问题。只是不怎么好看而且有些难以维护。

那么我们来看看有没有更好的方式吧。
本文所以的代码都在这个链接。 github.com/Link-X/veri…
首先代码未动,文档先行我们先来看下这一坨东西

// 变量
 var obj = {
     number: 1,
     string: 'str',
     array: [1, 2, 3],
     object: {a: 1},
     date: '2018-10-10 08:08'
  }
  
  // 校验规则
var rule = [
     number: [
        { required: true, message: '请输入number' }
     ],
     string: [
         { required: true, type: 'number' message: '请输入string' }
     ],
     array: [
         { message: '请输入array' },
         { min: 0, max: 3, message: '最小为0,最大为3' }
     ],
     object: [
         { required: false,  message: '请选择obj', validator: judgeObj }
     ],
     date: [
         { type: 'date' required: true, message: '校验时间是否合法时,type必须为date' }
     ]
  ]
  // 自定义校验规则
    function judgeObj (val, cb)  {
      console.log(val)
      if (val.a > 1) {
          cb()
      } esle {
          cb(new Error('不通过'))
      }
  }

现在我们的需求是有一个变量,有一个规则,规则对应的key去校验变量
总之就是配置校验,减少if else。拯救发际线。

那么激动人心的时候到了。开始搬砖写代码
文件中。为了让代码看着更简单,我们就当所有的人都不会使用出错,所以不会对传入的参数进行校验是否错误,有兴趣的同学可以自己提交

首先,定义一个class

class Verify {
    constructor (data, rules) {
        this.data = null
        this.rules = {}
        // 每次实例化的时候 把需要校验的规则和变量导入
        this.$init(data, rules)
    }
    $init (data, rules) {
        // 初始化函数
        this.data = data
        this.rules = rules
    }
    iterator (rules) {
        // 核心函数,校验所以的规则
        
        // 状态变量
        let status = {
            status: false,
            message: '',
            key: ''
        }
        // 循环迭代
        for (let v of Object.keys(rules)) {
            // 这里我们也简单点把,两个规则当一个去校验
            const judge = { ...this.rules[v][0], ...this.rules[v][1] }
            console.log(judge) // 此时我们就能获取到所有的规则啦
            console.log(this.data[v]) // 所有的数据
        }
    }
}

那么我们已经完成了初始化,并且能通过遍历获取到所有的规则和数据。接下来要做的事情就是,将他们一一对应,并且校验。

孩儿们,躁动起来
我们重点写一下这个 iterator 函数,这个写好了,就完成了一半

// 我们抽出两个函数来执行,自定义规则校验吧,不如iterator太大了
class Verify {
    ...
    ...
    iterator () {
        // 核心函数,校验所以的规则
        
        // 状态变量
        let status = {
            status: false,
            message: '',
            key: ''
        }
        // 循环迭代
        for (let v of Object.keys(rules)) {
            // 这里我们也简单点把,两个规则当一个去校验
            const judge = { ...this.rules[v][0], ...this.rules[v][1] }
            const val = this.data[v]
            if (toString.call(judge.validator) === '[object Function]') {
                // 自定义规则如果有的直接用自定义规则
                // 自定义校验,对于的处理方式
                judge.validator(val, ((status) => {
                // 自定义执行函数。目的就是为了把 状态变量给自定义规则
                    return (cb) => { 
                        status.status = !!(cb && cb.message)
                        status.message =  cb && cb.message
                     }
                })(status))
            } else if (judge.required) {
                // 自定义规则必填设置了 true
                status.status = this.verifyTop(judge, val) || this.verifyBottom(judge, val)
            } else if (!judge.required && judge.min && judge.max) {
                // 自定义规则设置里最大最小值
                status.status = val && this.verifyBottom(judge, val)
            }
            
            if (status.status) {
                // status 为true停止校验
                status.key = v
                status.message = status.message ? status.message : judge.message
                return
            }
        }
    }
}

好啦,这个时候核心函数 iteratior 已经写完了。接下来我们主要的任务就是处理 this.verifyTop 和 this.verifyBottom 这两个自定义规则校验。 这里两个函数需要用到很多,js变量的类型校验.

接下来我们新建一个文件,就叫utils.js吧,把所以的类型校验都放在那里。

// 直接上代码,简单粗暴.这些校验大家应该都不陌生吧。
export const isArray = (data) => {
    // 判断是否数组的
    return Object.prototype.toString.call(data) === '[object Array]'
}

export const isNumber = (data) => {
    // 判断是否数字的
    return Object.prototype.toString.call(data) === '[object Number]'
}

export const isString = (data) => {
    // 判断是否字符串的
    return Object.prototype.toString.call(data) === '[object String]'
}

export const isBoolean = (data) => {
    // 判断是否布尔值的
    return Object.prototype.toString.call(data) === '[object Boolean]'
}

export const isFunc = (data) => {
    // 判断是否函数的
    return Object.prototype.toString.call(data) === '[object Function]'
}

export const isObject = (data) => {
    // 判断是否函数的
    return Object.prototype.toString.call(data) === '[object Object]'
}

export const isNull = (data) => {
    // 判断是否null的
    return Object.prototype.toString.call(data) === '[object Null]'
}

export const arrayLen = (data) => {
    // 判断数组有木有长度
    return isArray(data) && data.length
}

export const objectLen = (data) => {
    // 判断对象有木有函数
    return isObject(data) && Object.keys(data).length
}

export const isDate = (data) => {
    // 判断是否合法时间的,这个有些不严谨哈
    return !!data && new Date(data).toString() !== 'Invalid Date'
}

export const verifyDate = (val) => {
    // 这个是判断,长度为2的数组,里是不是有两个时间。(时间范围的时候会用到)
    return isArray(val) ? (isDate(val[0]) && isDate(val[1])) : isDate(val)
}

export const getLen = (val) => {
    // 获取变量长度
    return val && val.length
}
export const getObjLen = (val) => {
    // 获取对象长度
    return Object.keys(val).length
}
export const getNumLen = (val) => {
    // 获取number
    return val
}

export const getType = (val) => {
    // 获取变量类型
    return (val && val.constructor.name.toLowerCase()) || 'string'
}

export const typeOfS = {
    array: isArray,
    object: isObject,
    number: isNumber,
    string: isString,
    boolean: isBoolean,
    date: verifyDate
}

export const getTypeLen = {
    array: getLen,
    object: getObjLen,
    number: getNumLen,
    string: getLen,
    date: getLen
}

接着我们会到 index.js。现在我们再改造一下verify 全貌是是这样的

import {
    objectLen,
    isObject,
    typeOfS,
    isFunc,
    getTypeLen,
    getType
} from './index.js'
class Verify {
    constructor(data, rules) {
        this.data = null
        this.rules = {}
        this.$init(data, rules)
    }
    $init(data, rules) {
        this.data = data
        this.rules = rules
    }
    verifyTop (obj, val) {
        // 校验第一个规则
        const type = obj.type ? obj.type : getType(val)
        const func = typeOfS[type]
        return !(val && func(val))
    }
    verifyBottom (obj, val) {
        // 校验第二个规则
        const section = obj.min && obj.max && obj.type !== 'date'
        if (!section) return false
        const type = getType(val)
        const len = getTypeLen[type](val)
        const lenSection = (len >= obj.min && len <= obj.max)
        return !lenSection
    }
    iterator (rules) {
        if (!isObject(rules)) {
            return
        }
        let status = {
            status: false,
            message: '',
            key: ''
        }
        for (let v of Object.keys(rules)) {
            const judge = { ...this.rules[v][0], ...this.rules[v][1] }
            const val = this.data[v]
            if (isFunc(judge.validator)) {
                // 自定义规则如果有的直接用自定义规则
                // 自定义校验,对于的处理方式
                judge.validator(val, ((status) => {
                // 自定义执行函数。目的就是为了把 状态变量给自定义规则
                    return (cb) => { 
                        status.status = !!(cb && cb.message)
                        status.message =  cb && cb.message
                     }
                })(status))
            } else if (judge.required) {
                status.status = this.verifyTop(judge, val) || this.verifyBottom(judge, val)
            } else if (!judge.required && judge.min && judge.max) {
                status.status = val && this.verifyBottom(judge, val)
            }
            if (status.status) {
                // status 为true停止校验
                status.key = v
                status.message = status.message ? status.message : judge.message
                return status
            }
        }
        return status
    }
    validate (cb) {
        const status = this.iterator(this.rules)
        const result = status.status || status.message
        cb({
            result: result,
            key: status.key,
            message: status.message
        })
    }
}

okok 大功告成。使用方式

用法

<script src="./utils.js"></script>
<script src="./index.js"></script>
var obj = {
    number: 1,
    string: 'str',
    array: [1, 2, 3],
    object: {a: 1},
    date: '2018-10-10 08:08'
 }
 var rule = [
    number: [
       { required: true, message: '请输入number' }
    ],
    string: [
        { required: true, type: 'number' message: '请输入string' }
    ],
    array: [
        { message: '请输入array' },
        { min: 0, max: 3, message: '最小为0,最大为3' }
    ],
    object: [
        { required: false,  message: '请选择obj', validator: judgeObj }
    ],
    date: [
        { type: 'date' required: true, message: '校验时间是否合法时,type必须为date' }
    ]
 ]

var judgeObj (val, cb)  {
     console.log(val)
     if (val.a > 1) {
         cb()
     } esle {
         cb(new Error('不通过'))
     }
 }
var main = new verify(obj, rule);

main.validate((e) => {
    if (e.result) {
        alert('succes')
        return
    }
    alert('err')
    console.log(e.key + 'err' + e.message)
})


main.$init(data, rule)

注意,如果你将需要校验的值重新覆盖了,或者校验规则重新覆盖了,而没有调用$init 的话,将无法校验