如何封装一个带表单验证的 Vue的表单插件(三):优化和拓展

上一篇我们已经完善了主要的验证功能~包括单独验证、不通触发方式、全部交验,但是我只用了输入框组件,但是

  • 表单组件当中除了输入框,还有多行输入textarea(这个和input.vue基本一样,这边不再次写了)。单选(radio)、多选(CheckBox)、下拉选择(select)这些个常用标签,其中还是有一些细微差别,我们还要拿出来简单说一下;
  • 此外我们上篇的文章中只是使用的基本的必填验证和 async-validator自带的验证规则。本章节会完善自定义验证规则~

废话不多说,开始我们的代码~~

自定义校验

我们在src下新建一个目录utils,放工具JS:
在这里插入图片描述
其中

  • assist.js 是第一篇文章说道的工具函数 findComponentUpward(向上查找最近的最近的指定组件) 、findComponentsDownward(向下查找所有指定的组件 )。
  • constant.js放的是一些常量,使用全大写定义,可以更好管理常量,并且在书写的时候更容易通过编译器只能识别错写的问题。
  • validateRules.js 就是表单验证rules的JS文件,里面包括了一些内部处理函数 和方法。向外暴露(export) 对应的form表单的rule规则

让我们来详细看一下validateRules.js :

import * as constant from './constant'

/**
 * 去除前后空格
 * @param {*} val 字符串
 */
function trim (val) {
  return val.replace(/(^\s*)|(\s*$)/g, '')
}
/**
 * 字符串长度范围验证
 * @param {*} min 最少位数 默认0
 * @param {*} max 最多位数 默认10000
 */
function lengthLimit (min = 0, max = 1000000) {
  if (arguments.length === 0) {
    return
  }
  const isNumber = [...arguments].every(item => typeof item === 'number')
  if (!isNumber) {
    throw new Error('lengthLimit函数参数必须为Number')
  }
  const message = arguments.length === 1 ? `长度必须大于${min}` : `长度必须大于${min}、小于${max}`
  return {
    type: 'string',
    message: message,
    validator: (rule, val) => {
      const value = trim(val)
      return value.length >= min && value.length <= max
    },
    trigger: constant.BLUR
  }
}

// 以下是自定义验证规则

// true/false判断 适合 单选 单多选 select等~
const checked = (rule, value) => value
const selected = (rule, value) => value !== ''

const isFullName = (rule, val) => {
  const value = trim(val)
  return /\w/.test(value)
}
const hobby = (rule, val) => val.length >= 2

const sexValidate = (rule, val) => val.length > 0

export const loginRules = {
  name: [
    {
      required: true,
      message: '用户名不能为空',
      trigger: constant.BLUR
    },
    {
      type: 'string',
      message: '用户名只能是数字、字母或下划线',
      validator: isFullName,
      trigger: constant.BLUR
    },
    lengthLimit(4, 18)
  ],
  checkedArr: [
    {
      type: 'array',
      message: '至少勾选2项',
      required: true,
      validator: hobby,
      trigger: constant.CHANGE
    }
  ],
  professional: [
    {
      type: 'string',
      message: '请选择专业',
      required: true,
      validator: selected,
      trigger: constant.CHANGE
    }
  ],
  sex: [
    {
      type: 'number',
      message: '请选择性别',
      required: true,
      validator: sexValidate,
      trigger: constant.CHANGE
    }
  ],
  own: [
    {
      type: 'string',
      message: '请勾选用户条款',
      required: true,
      validator: checked,
      trigger: constant.CHANGE
    }
  ],
  email: [
    {
      required: true,
      message: '邮箱不能为空',
      trigger: constant.BLUR
    },
    {
      type: 'email',
      message: '邮箱格式不正确',
      trigger: constant.BLUR
    }
  ]
}

我们将一些可能会重复使用到的验证规则 写成js文件内部的方法,哪里需要哪里使用;一般项目中会有很多地方都需要使用表单+验证,比如登录、注册、修改/忘记密码等~,将验证规则统一写在一个JS文件中统一管理,按需引用。这样之前的index.vue文件中就可以改成

// 部分代码省略
<script>
import Emitter from '@/mixins/emitter'
import { loginRules } from '@/utils/validateRules'
export default {
  mixins: [Emitter],
  name: 'index',
  data () {
    return {
      validateRules: loginRules
      }
    }

接下来我们来完善其他几个表单元素组件的编写~

checkbox 与 checkbox-group

多选框使用场景很多,单个出现使用一般出现在同意协议这种情况~ 成组出现一般在 多条件筛选、兴趣爱好选择等情况。所以我们编写CheckBox组件的时候得将这种情况考虑进去。
首先我们在base文件夹建一个checkbox文件夹,里面建两个文件:checkbox.vue 和 checkbox-group.vue。
我们先看checkbox.vue:

<template>
  <label>
    <span>
      <input
        v-if="group"
        type="checkbox"
        :value="label"
        :disabled="disabled"
        v-model="model"
        @change="handleChange"
      >
      <input
        v-else
        type="checkbox"
        :disabled="disabled"
        :checked="currentValue"
        @change="handleChange"
      >
    </span>
    <slot></slot>
  </label>
</template>
<script>
import Emitter from '@/mixins/emitter'
import { findComponentUpward } from '@/utils/assist'
export default {
  mixins: [Emitter],
  name: 'w-checkbox',
  props: {
    disabled: {
      type: Boolean,
      default: false
    },
    value: {
      type: [String, Number, Boolean],
      default: false
    },
    label: {
      type: [String, Number, Boolean]
    },
    trueValue: {
      type: [String, Number, Boolean],
      default: true
    },
    falseValue: {
      type: [String, Number, Boolean],
      default: false
    }
  },
  data () {
    return {
      currentValue: this.value,
      model: [],
      group: false,
      parent: null
    }
  },
  watch: {
    value (val) {
      if (val === this.trueValue || val === this.falseValue) {
        this.updateModel()
      } else {
        throw new Error('Value should be trueValue or falseValue')
      }
    }
  },
  mounted () {
    this.parent = findComponentUpward(this, 'w-checkbox-group')
    if (this.parent) {
      this.group = true
    }
    if (this.group) {
      this.parent.updateModel(true)
    } else {
      this.updateModel()
    }
  },
  methods: {
    handleChange (e) {
      if (this.disabled) {
        return
      }
      const checked = e.target.checked
      this.currentValue = checked
      const value = checked ? this.trueValue : this.falseValue
      this.$emit('input', value)
      if (this.group) {
        this.parent.change(this.model)
      } else {
        this.$emit('on-change', value)
        this.dispatch('form-item', 'on-form-change', value)
      }
    },
    updateModel () {
      this.currentValue = this.value === this.trueValue
    }
  }
}
</script>

chekbox.vue中我们相当于写了两套代码,适配于是否有 checkbox-group.vue父级,如果有代表CheckBox是成组出现的,使用我们之前说过的方法 findComponentUpward。

重点说几点:

  • props中的trueValue和falseValue是增强扩展性的,一般数据库中存入的是/否 判断 不会直接是 true/false。可能是 0和1 或者其他字符串,所以用户可以通过这两个属性定义;不用每次在代码里面转换完之后再传入。
  • 如果是group模式,通过data中定义的model 和 props中的label ,然后通过this.parent.change(this.model)向上级更新,实现双向数据绑定
  • 如果是在form表单中单独使用checkbox.vue ,使用自定义方法 dispatch 将change的响应暴露给form-item。(form-item添加了对change事件的监听与回调,请直接查看GitHub上面的源码)

接下来我们看checkbox-group.vue文件:

<template>
  <div class="checkbox-group">
    <slot></slot>
  </div>
</template>
<script>
import Emitter from '@/mixins/emitter'
import { findComponentsDownward } from '@/utils/assist'
export default {
  mixins: [Emitter],
  name: 'w-checkbox-group',
  props: {
    value: {
      type: Array,
      default: () => []
    }
  },
  data () {
    return {
      currentValue: this.value,
      childrens: []
    }
  },
  watch: {
    value (val) {
      this.updateModel(true)
    }
  },
  mounted () {
    this.updateModel(true)
  },
  methods: {
    updateModel (update) {
      this.childrens = findComponentsDownward(this, 'w-checkbox')
      if (this.childrens.length > 0) {
        const { value } = this
        this.childrens.forEach(child => {
          child.model = value
        })
      }
    },
    change (data) {
      this.currentValue = data
      this.$emit('input', data)
      this.$emit('on-change', data)
      this.dispatch('form-item', 'on-form-change', data)
    }
  }
}
</script>

  • 通过findComponentsDownward方法找到所有的checkbox元素,然后将外部组件上的v-model的属性(checkbox-group中props中的value)赋值给每一个 checkbox.vue中的data的model。(关于多个checkbox在Vue中双向数据绑定的问题可以直接查看官方文档 复选框
  • 使用自定义方法 dispatch 将change的响应暴露给form-item。

radio单选框

单选框在表单中经常出现,例如性别或者 是否是xxx的选择。我们在base下 新建 radio/radio.vue。

<template>
  <label>
    <input
      type="radio"
      :name="name"
      v-model="model"
      :value="currentValue"
      :disabled="disabled"
      @change="handleChange"
    >
    <slot></slot>
  </label>
</template>
<script>
import Emitter from '@/mixins/emitter'
export default {
  mixins: [Emitter],
  name: 'w-radio',
  props: {
    name: {
      type: String
    },
    value: {
      type: [String, Number]
    },
    label: {
      type: [String, Number]
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data () {
    return {
      currentValue: this.label,
      model: this.value
    }
  },
  methods: {
    handleChange (e) {
      const val = e.target.value
      this.model = val
      this.$emit('input', val)
      this.dispatch('form-item', 'on-form-change', val)
    }
  }
}
</script>

单选组件相对来说就简单很多了(偷了个懒o(╯□╰)o,为了增强扩展性,可以和checkbox一样增加trueValue和falseValue两个属性);需要用到多个radio就在引用组件的时候外面套个循环用的标签~

select组件

下拉框也是使用频率非常高的标签;我们在base下 新建 select/select.vue。

<template>
  <select v-model="currentValue" @change="handleChange">
    <option value>全部</option>
    <slot></slot>
  </select>
</template>
<script>
import Emitter from '@/mixins/emitter'
export default {
  mixins: [Emitter],
  name: 'WSelect',
  props: {
    value: {
      type: String
    }
  },
  data () {
    return {
      currentValue: this.value
    }
  },
  methods: {
    handleChange (e) {
      const val = e.target.value
      this.currentValue = val
      this.$emit('input', val)
      this.dispatch('form-item', 'on-form-change', val)
    }
  }
}
</script>

这个组件主要就有一个select标签,内部默认有一个空value的option,默认选择“全部”(根据需要可有可无).
其他正式选项都通过<slot>标签插入。

————————————————————————————————————————————————

总结

简单来说,我们的form表单组件到这里就已经很完整了,效果就是如第一篇文章刚开始那个动图一样。
大家如果有想法,可以去我的GitHub上面查看源码

有什么问题可以随时给我留言,欢迎一起讨论,共同进步。

其实代码还有很多的不足,我也只是个龟速学习的菜鸟,希望路过的大神可以多几句提点,感激不尽。

参考文章

Vue.js 组件精讲

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值