Vue Validate表单组件的封装详解
作者:追梦杰尼龟
之前一直是直接去使用别人现成的组件库,也没有具体去了解人家的组件是怎么封装的,造轮子才会更好地提高自己,所以尝试开始从封装Form表单组件开始
一:组件需求分析
本次封装组件,主要是摸索封装组件的流程,对于具体需要的方法和属性,只会实现其中部分方法和属性,之后一点一点才进行添加
表单项组件,ValidateInput组件的封装
- 根据传递不同的type类型有着不同的校验规则
- 支持V-model,对于封装的自定义组件支持v-model双向绑定也是一个很关键的属性
- tag根节点的类型
form组件,提交事件
- 验证整体表单项,是否通过
- 清空表单项的内容
二:技术栈
Vue3
TS
Bootstrap样式库
三:封装Validate-input验证表单项组件
思路
1. 明确属性和事件
- v-model属性
- rules属性:根据不同type不同校验规则
- tag根节点类型
ValidateInput组件
<template> <div class="validate-input-container pb-3"> <input class="form-control" :class="{ 'is-invalid': inputRef.error }" :value="modelValue" @blur="validateInput" @input="updateValue" v-bind="$attrs" /> <span v-if="inputRef.error" class="invalid-feedback">{{ inputRef.message }}</span> </div> </template>
<script setup lang="ts"> //禁用 Attributes 继承 defineOptions({ inheritAttrs: false }) //定义传来的一个参数类型 interface RangeProp { message: string length: number } interface RuleProp { type: 'required' | 'email' | 'range' message?: string //至少位数 min?: RangeProp max?: RangeProp } //数组类型 export type RulesProp = RuleProp[] //接收参数 const props = defineProps<{ rules?: RulesProp //自定义组件使用v-model需要用modelValue来接收参数 modelValue: string }>() //定义表单的数据 const inputRef = reactive({ //如果为空 val: props.modelValue || '', error: false, //表单验证是否通过 message: '' //错误信息 }) </script>
禁用Attributes继承:透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on
事件监听器。最常见的例子就是 class
、 style
和 id
。
<!-- <MyButton> 的模板 --> <button>click me</button>
当父组件使用该组件,并且传入class:
<MyButton class="large" />
最终会在根元素出现class=‘large“, <MyButton>
并没有将 class
声明为一个它所接受的 prop,所以 class
被视作透传 attribute,自动透传到了 <MyButton>
的根元素上。
因此需要禁用Attibutes继承
定义接收参数类型
- rules可选参数,接收数组
- modelValue:接收字符串即输入的值
2. v-model属性
在使用一个自定义组件给其添加v-model属性,其自定义组件内部做了两件事
将内部原生的input元素的值绑定到组件内部Prop属性modelValue
当原生input元素触发时候,触发一个携带了新值的 update:modelValue
自定义事件
<!-- 自定义组件Validate-input --> <Validate-input v-model="loginParams.email" :rules="emailRules" placeholder="请输入邮箱地址"></Validate-input>
Validate-input组件
//接收参数 const props = defineProps<{ rules?: RulesProp //自定义组件使用v-model需要用modelValue来接收参数 modelValue: string }>()
props接收了,modelValue属性,类型string
<input class="form-control" :class="{ 'is-invalid': inputRef.error }" :value="modelValue" @blur="validateInput" @input="updateValue" v-bind="$attrs" />
设置了自定义事件用于更新value
//手动更新value const updateValue = (e: Event) => { //HTMLInputElement输入元素类型 let targetValue = (e.target as HTMLInputElement).value //更新文本框的值 inputRef.val = targetValue emit('update:modelValue', targetValue) }
因此实现v-model属性
3. rules属性
接收参数
- type:校验类型,requied | email | range
- message
- min:至少几位
- max:至多几位
抽象验证逻辑
validate-input组件
//数组类型 export type RulesProp = RuleProp[] //接收参数 const props = defineProps<{ rules?: RulesProp //自定义组件使用v-model需要用modelValue来接收参数 modelValue: string }>()
参数接收rules是个数组
//定义表单的数据 const inputRef = reactive({ //如果为空 val: props.modelValue || '', error: false, //表单验证是否通过 message: '' //错误信息 })
定义表单的数据
//定义事件 const validateInput = () => { //定义邮箱的正则 let emailReg = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/ //如果传过来的有验证的话 if (props.rules) { //every方法 遍历全部数组只有全部满足条件才会返回true, 只要有一个false停止遍历 let allRulePassed = props.rules.every(rule => { let passed = true //消息赋值 类型断言 inputRef.message = rule.message as string //switch选择对应type类型错误进行校验 switch (rule.type) { case 'required': if (inputRef.val.trim() === '') { passed = false } break case 'email': if (!emailReg.test(inputRef.val)) { passed = false } break case 'range': passed = validateRange(rule.min, rule.max) break } return passed }) //allRulePassed为false表示通过 //所以Input.error表示错误应该为true inputRef.error = !allRulePassed return allRulePassed } return true }
定义校验事件
满足全部rules校验才可以通过,因此用到es6数组方法,every只有全部项都为true才会遍历全部返回true,只要有一个项结果为false,就会停止遍历
message校验信息赋值给inputRef.message
利用swicth case语句,选择对应的type类型进行校验
需要充分考虑到所有情况,通过passed为true,未通过为fasle
range长度校验,由于情况较多,单独封装一个函数去校验
//校验长度 const validateRange = (min: RangeProp | undefined, max: RangeProp | undefined) => { let passed = true //1. 如果min 存在 ,max不存在 if (min && !max) { inputRef.message = min.message passed = !(inputRef.val.length < min.length) } //2. min不在, max在 if (!min && max) { inputRef.message = max.message passed = !(inputRef.val.length > max.length) } //3. min在 max在 if (min && max) { //若小于 if (inputRef.val.length < min.length) { passed = false inputRef.message = min.message } //若大于 if (inputRef.val.length > max.length) { passed = false inputRef.message = max.message } } return passed }
模板中,根据passed的值,动态绑定未通过的样式
4. 默认属性
validate-input组件
<div class="validate-input-container pb-3"> <input class="form-control" :class="{ 'is-invalid': inputRef.error }" :value="modelValue" @blur="validateInput" @input="updateValue" v-bind="$attrs" /> <span v-if="inputRef.error" class="invalid-feedback">{{ inputRef.message }}</span> </div>
父组件
<Validate-input v-model="loginParams.password" :rules="passwordRules" type="password" placeholder="请输入密码" ></Validate-input>
通过显示绑定attrs,使得自定义组件可以使用默认属性
在 ValidateInput
组件中,可以通过 $attrs
属性将 type
属性将被传递到组件的根元素上
可以在父组件中灵活地传递任何HTML属性给 ValidateInput
组件,使它更加通用和可配置
四:封装Validate-Form组件
事件
form-submit:点击提交触发的事件,回调参数result是布尔值,表示该表单是否通过了校验
1. form-submit事件
当点击提交时,要去验证该表单是否通过了校验,就需要一个个将表单项进行校验,只要一个没通过就返回false,只有全部通过返回true
收集全部表单项校验函数
可以通过将所有表单项的校验函数都添加到一个数组中,然后最终通过every方法遍历是否全部通过
收集
安装mitt并使用,监听事件
npm install mitt --save
全局挂载
main.ts
import mitt from 'mitt' //对外暴露全局事件总线实例 export const bus = mitt()
ValidateForm组件中使用
<script setup lang="ts"> import { onUnmounted, reactive } from 'vue' import { bus } from '@/main' const emits = defineEmits(['form-submit']) //点击事件 //定义函数类型 type ValidateFunc = () => boolean //定义类型 //定义接收的函数数组 const funcArr = reactive<ValidateFunc[]>([]) //测试回调 const callback = (func: ValidateFunc) => { //将每个校验函数加入数组 funcArr.push(func) } //订阅事件 bus.on('form-item-created', callback as ValidateFunc) //提交按钮触发事件 const submitForm = () => { const result = funcArr.map(func => func()).every(result => result) // 触发提交事件 //遍历funcArr中的每个校验函数 emits('form-submit', result) } onUnmounted(() => { // 移除订阅 bus.off('form-item-created', callback as ValidateFunc) }) </script>
- 定义函数类型,返回值为空
- 利用泛型定义存放每个表单项校验函数存放的数组
- 订阅事件,在组件卸载的时候再取消订阅
ValidateInput 组件中
组件挂载的时候,就直接触发自定义事件,然后将每一项的校验函数传递给 ValidateForm组件
中
Validate-Form组件
然后接受ValidateInput组件传递过来的校验函数,一个个加入到数组中
在submitForm事件中,遍历数组,不能直接使用every方法,因为只要一个不通过就不进行后面的校验,这是不满足我们的需求的
可以看到下面的错误实例,密码原本也是不通过校验的,但是every方法直接在第一个校验失败结束遍历了
因此先利用map函数,先使得每个校验函数都执行,结束后返回一个新的数组存放校验函数的返回值,再通过every遍历数组
最后触发自定义事件,将结果传递给父组件中
父组件
<Validate-Form @form-submit="submitForm" ref="validateFormRef">
const submitForm = (result: boolean) => { console.log(result) }
提交表单元素后清空表单值
具体逻辑跟校验差不多
Validate-Form组件
<script setup lang="ts"> import { onUnmounted, reactive } from 'vue' import { bus } from '@/main' const emits = defineEmits(['form-submit']) //点击事件 //定义函数类型 type ValidateFunc = () => boolean //定义清空Input函数类型 type clearInputsFunc = () => void //定义类型 //定义接收的函数数组 const funcArr = reactive<ValidateFunc[]>([]) //定义接收用于清空的函数数组 const clearFuncArr = reactive<clearInputsFunc[]>([]) //测试回调 const callback = (func: ValidateFunc) => { //将每个校验函数加入数组 funcArr.push(func) } //事件回调 const clearInputFunc = (func: clearInputsFunc) => { clearFuncArr.push(func) } //绑定监听事件 bus.on('form-item-created', callback as ValidateFunc) bus.on('form-item-clear', clearInputFunc as clearInputsFunc) //提交按钮触发事件 const submitForm = () => { const result = funcArr.map(func => func()).every(result => result) // 触发提交事件 //遍历funcArr中的每个校验函数 emits('form-submit', result) //遍历清空函数数组并依次并执行 //当校验通过时候才会清空input if (result) { clearFuncArr.map(func => func()) } } onUnmounted(() => { // 移除事件监听器 bus.off('form-item-created', callback as ValidateFunc) bus.off('form-item-clear', clearInputFunc as clearInputsFunc) }) </script>
Validate-input组件
onMounted(() => { //直接把validateInput校验事件传递过去 bus.emit('form-item-created', validateInput) //直接触发事件先传入每个input的值 bus.emit('form-item-clear', clearInput) }) //定义表单的数据 const inputRef = reactive({ //如果为空 val: props.modelValue || '', error: false, //表单验证是否通过 message: '' //错误信息 })
把清空表单数据的处理函数收集起来
最后利用map方法依次执行清除即可
五:演示和使用
在 vue template 中添加结构代码
<Validate-Form @form-submit="submitForm" ref="validateFormRef"> <!-- 邮箱地址 --> <div class="mb-3"> <label for="exampleInputEmail1" class="form-label">邮箱地址</label> <!-- 自定义组件Validate-input --> <Validate-input v-model="loginParams.email" :rules="emailRules" placeholder="请输入邮箱地址"></Validate-input> </div> <!-- 密码 --> <div class="mb-3"> <label for="exampleInputPassword1" class="form-label">密码</label> <Validate-input v-model="loginParams.password" :rules="passwordRules" type="password" placeholder="请输入密码" ></Validate-input> </div> <template #submit> <button type="submit" class="btn btn-primary btn-block btn-large">提交</button> </template> </Validate-Form>
在 setup语法糖中添加数据
//定义验证类型数据 const emailRules: RulesProp = [ { type: 'required', message: '电子邮箱地址不能为空' }, { type: 'email', message: '请输入正确的电子邮箱格式' } ] //定义密码验证类型数据 const passwordRules: RulesProp = [ { type: 'required', message: '密码不能为空' }, { type: 'range', min: { message: '你的密码至少包括6位,不能含有空格', length: 6 }, max: { message: '你的密码至多15位,不能含有空格', length: 15 } } ] //监听结果 const submitForm = (result: boolean) => { console.log(result) }
Validate-Form属性和事件
form-submit:
类型:事件
默认:-
说明:回调参数 (result: boolean) => void, result 代表是否通过了验证
Validate-Input属性和事件
rules
- 类型:array
- 默认:-
- 说明: 单个输入框的验证类型,可以传入包含特定对象的数组, 详情可见上面示例代码
tag
- 类型:input | textarea
- 默认:input
- 说明: 根节点类型
v-model
- 类型: string
- 默认:-
- 说明: 支持 v-model,请对响应式数据
到此这篇关于Vue Validate表单组件的封装详解的文章就介绍到这了,更多相关Vue Validate内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!