Vue?Validate表單組件的封裝詳解
之前一直是直接去使用別人現(xiàn)成的組件庫,也沒有具體去了解人家的組件是怎么封裝的,造輪子才會更好地提高自己,所以嘗試開始從封裝Form表單組件開始
一:組件需求分析
本次封裝組件,主要是摸索封裝組件的流程,對于具體需要的方法和屬性,只會實現(xiàn)其中部分方法和屬性,之后一點一點才進行添加

表單項組件,ValidateInput組件的封裝
- 根據(jù)傳遞不同的type類型有著不同的校驗規(guī)則
- 支持V-model,對于封裝的自定義組件支持v-model雙向綁定也是一個很關鍵的屬性
- tag根節(jié)點的類型
form組件,提交事件
- 驗證整體表單項,是否通過
- 清空表單項的內(nèi)容
二:技術棧
Vue3
TS
Bootstrap樣式庫
三:封裝Validate-input驗證表單項組件
思路
1. 明確屬性和事件
- v-model屬性
- rules屬性:根據(jù)不同type不同校驗規(guī)則
- tag根節(jié)點類型
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
})
//定義傳來的一個參數(shù)類型
interface RangeProp {
message: string
length: number
}
interface RuleProp {
type: 'required' | 'email' | 'range'
message?: string
//至少位數(shù)
min?: RangeProp
max?: RangeProp
}
//數(shù)組類型
export type RulesProp = RuleProp[]
//接收參數(shù)
const props = defineProps<{
rules?: RulesProp
//自定義組件使用v-model需要用modelValue來接收參數(shù)
modelValue: string
}>()
//定義表單的數(shù)據(jù)
const inputRef = reactive({
//如果為空
val: props.modelValue || '',
error: false, //表單驗證是否通過
message: '' //錯誤信息
})
</script>禁用Attributes繼承:透傳 attribute”指的是傳遞給一個組件,卻沒有被該組件聲明為 props 或 emits 的 attribute 或者 v-on 事件監(jiān)聽器。最常見的例子就是 class 、 style 和 id 。
<!-- <MyButton> 的模板 --> <button>click me</button>
當父組件使用該組件,并且傳入class:
<MyButton class="large" />
最終會在根元素出現(xiàn)class=‘large“, <MyButton> 并沒有將 class 聲明為一個它所接受的 prop,所以 class 被視作透傳 attribute,自動透傳到了 <MyButton> 的根元素上。
因此需要禁用Attibutes繼承
定義接收參數(shù)類型
- rules可選參數(shù),接收數(shù)組
- modelValue:接收字符串即輸入的值
2. v-model屬性
在使用一個自定義組件給其添加v-model屬性,其自定義組件內(nèi)部做了兩件事
將內(nèi)部原生的input元素的值綁定到組件內(nèi)部Prop屬性modelValue
當原生input元素觸發(fā)時候,觸發(fā)一個攜帶了新值的 update:modelValue 自定義事件
<!-- 自定義組件Validate-input -->
<Validate-input v-model="loginParams.email" :rules="emailRules" placeholder="請輸入郵箱地址"></Validate-input>Validate-input組件
//接收參數(shù)
const props = defineProps<{
rules?: RulesProp
//自定義組件使用v-model需要用modelValue來接收參數(shù)
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)
}因此實現(xiàn)v-model屬性
3. rules屬性
接收參數(shù)
- type:校驗類型,requied | email | range
- message
- min:至少幾位
- max:至多幾位
抽象驗證邏輯
validate-input組件
//數(shù)組類型
export type RulesProp = RuleProp[]
//接收參數(shù)
const props = defineProps<{
rules?: RulesProp
//自定義組件使用v-model需要用modelValue來接收參數(shù)
modelValue: string
}>()參數(shù)接收rules是個數(shù)組
//定義表單的數(shù)據(jù)
const inputRef = reactive({
//如果為空
val: props.modelValue || '',
error: false, //表單驗證是否通過
message: '' //錯誤信息
})定義表單的數(shù)據(jù)
//定義事件
const validateInput = () => {
//定義郵箱的正則
let emailReg = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/
//如果傳過來的有驗證的話
if (props.rules) {
//every方法 遍歷全部數(shù)組只有全部滿足條件才會返回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數(shù)組方法,every只有全部項都為true才會遍歷全部返回true,只要有一個項結(jié)果為false,就會停止遍歷
message校驗信息賦值給inputRef.message
利用swicth case語句,選擇對應的type類型進行校驗
需要充分考慮到所有情況,通過passed為true,未通過為fasle
range長度校驗,由于情況較多,單獨封裝一個函數(shù)去校驗
//校驗長度
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
}
模板中,根據(jù)passed的值,動態(tài)綁定未通過的樣式
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:點擊提交觸發(fā)的事件,回調(diào)參數(shù)result是布爾值,表示該表單是否通過了校驗
1. form-submit事件
當點擊提交時,要去驗證該表單是否通過了校驗,就需要一個個將表單項進行校驗,只要一個沒通過就返回false,只有全部通過返回true
收集全部表單項校驗函數(shù)
可以通過將所有表單項的校驗函數(shù)都添加到一個數(shù)組中,然后最終通過every方法遍歷是否全部通過
收集
安裝mitt并使用,監(jiān)聽事件
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'])
//點擊事件
//定義函數(shù)類型
type ValidateFunc = () => boolean
//定義類型
//定義接收的函數(shù)數(shù)組
const funcArr = reactive<ValidateFunc[]>([])
//測試回調(diào)
const callback = (func: ValidateFunc) => {
//將每個校驗函數(shù)加入數(shù)組
funcArr.push(func)
}
//訂閱事件
bus.on('form-item-created', callback as ValidateFunc)
//提交按鈕觸發(fā)事件
const submitForm = () => {
const result = funcArr.map(func => func()).every(result => result)
// 觸發(fā)提交事件
//遍歷funcArr中的每個校驗函數(shù)
emits('form-submit', result)
}
onUnmounted(() => {
// 移除訂閱
bus.off('form-item-created', callback as ValidateFunc)
})
</script>- 定義函數(shù)類型,返回值為空
- 利用泛型定義存放每個表單項校驗函數(shù)存放的數(shù)組
- 訂閱事件,在組件卸載的時候再取消訂閱
ValidateInput 組件中
組件掛載的時候,就直接觸發(fā)自定義事件,然后將每一項的校驗函數(shù)傳遞給 ValidateForm組件 中
Validate-Form組件
然后接受ValidateInput組件傳遞過來的校驗函數(shù),一個個加入到數(shù)組中
在submitForm事件中,遍歷數(shù)組,不能直接使用every方法,因為只要一個不通過就不進行后面的校驗,這是不滿足我們的需求的
可以看到下面的錯誤實例,密碼原本也是不通過校驗的,但是every方法直接在第一個校驗失敗結(jié)束遍歷了

因此先利用map函數(shù),先使得每個校驗函數(shù)都執(zhí)行,結(jié)束后返回一個新的數(shù)組存放校驗函數(shù)的返回值,再通過every遍歷數(shù)組
最后觸發(fā)自定義事件,將結(jié)果傳遞給父組件中
父組件
<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'])
//點擊事件
//定義函數(shù)類型
type ValidateFunc = () => boolean
//定義清空Input函數(shù)類型
type clearInputsFunc = () => void
//定義類型
//定義接收的函數(shù)數(shù)組
const funcArr = reactive<ValidateFunc[]>([])
//定義接收用于清空的函數(shù)數(shù)組
const clearFuncArr = reactive<clearInputsFunc[]>([])
//測試回調(diào)
const callback = (func: ValidateFunc) => {
//將每個校驗函數(shù)加入數(shù)組
funcArr.push(func)
}
//事件回調(diào)
const clearInputFunc = (func: clearInputsFunc) => {
clearFuncArr.push(func)
}
//綁定監(jiān)聽事件
bus.on('form-item-created', callback as ValidateFunc)
bus.on('form-item-clear', clearInputFunc as clearInputsFunc)
//提交按鈕觸發(fā)事件
const submitForm = () => {
const result = funcArr.map(func => func()).every(result => result)
// 觸發(fā)提交事件
//遍歷funcArr中的每個校驗函數(shù)
emits('form-submit', result)
//遍歷清空函數(shù)數(shù)組并依次并執(zhí)行
//當校驗通過時候才會清空input
if (result) {
clearFuncArr.map(func => func())
}
}
onUnmounted(() => {
// 移除事件監(jiān)聽器
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)
//直接觸發(fā)事件先傳入每個input的值
bus.emit('form-item-clear', clearInput)
})
//定義表單的數(shù)據(jù)
const inputRef = reactive({
//如果為空
val: props.modelValue || '',
error: false, //表單驗證是否通過
message: '' //錯誤信息
})把清空表單數(shù)據(jù)的處理函數(shù)收集起來
最后利用map方法依次執(zhí)行清除即可
五:演示和使用

在 vue template 中添加結(jié)構(gòu)代碼
<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語法糖中添加數(shù)據(jù)
//定義驗證類型數(shù)據(jù)
const emailRules: RulesProp = [
{ type: 'required', message: '電子郵箱地址不能為空' },
{ type: 'email', message: '請輸入正確的電子郵箱格式' }
]
//定義密碼驗證類型數(shù)據(jù)
const passwordRules: RulesProp = [
{ type: 'required', message: '密碼不能為空' },
{
type: 'range',
min: { message: '你的密碼至少包括6位,不能含有空格', length: 6 },
max: {
message: '你的密碼至多15位,不能含有空格',
length: 15
}
}
]
//監(jiān)聽結(jié)果
const submitForm = (result: boolean) => {
console.log(result)
}Validate-Form屬性和事件
form-submit:
類型:事件
默認:-
說明:回調(diào)參數(shù) (result: boolean) => void, result 代表是否通過了驗證
Validate-Input屬性和事件
rules
- 類型:array
- 默認:-
- 說明: 單個輸入框的驗證類型,可以傳入包含特定對象的數(shù)組, 詳情可見上面示例代碼
tag
- 類型:input | textarea
- 默認:input
- 說明: 根節(jié)點類型
v-model
- 類型: string
- 默認:-
- 說明: 支持 v-model,請對響應式數(shù)據(jù)
到此這篇關于Vue Validate表單組件的封裝詳解的文章就介紹到這了,更多相關Vue Validate內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue中關于element的el-image 圖片預覽功能增加一個下載按鈕(操作方法)
這篇文章主要介紹了vue中關于element的el-image 圖片預覽功能增加一個下載按鈕,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04

