VUE中使用TypeScript裝飾器實(shí)現(xiàn)表單驗(yàn)證的全過(guò)程
前言
最近接觸了關(guān)于很多TypeScript裝飾器的知識(shí),以及class-validator這個(gè)用裝飾器來(lái)做表單驗(yàn)證的包,就萌生了想在vue中使用裝飾器來(lái)做表單驗(yàn)證的想法。class-validator允許我們?cè)陬惿贤ㄟ^(guò)使用裝飾器來(lái)完成表單的驗(yàn)證,并且可在瀏覽器端和node端同時(shí)使用。那么接下來(lái)先簡(jiǎn)單介紹一下裝飾器和class-validator的用法。
裝飾器
裝飾器的語(yǔ)法十分簡(jiǎn)單,只需要在想使用的裝飾器前加上@符號(hào),裝飾器就會(huì)被應(yīng)用到目標(biāo)上。 通過(guò)裝飾器我們可以輕松實(shí)現(xiàn)代理模式來(lái)使代碼更簡(jiǎn)潔以及實(shí)現(xiàn)其它一些更有趣的能力。 關(guān)于裝飾器的用法我用代碼來(lái)簡(jiǎn)單的舉例幾個(gè),更詳細(xì)的信息大家可自行去網(wǎng)絡(luò)上查找。
// 比如我們有一個(gè)創(chuàng)建用戶的表單,在上面已經(jīng)應(yīng)用了一個(gè)Reactive類裝飾器
// 用了這個(gè)裝飾器后,這個(gè)類實(shí)例出來(lái)的對(duì)象會(huì)是響應(yīng)式對(duì)象了。
@Reactive()
export class CreateUserForm {
username:string
email:string
password:string
confirmPassword:string
}之后我們?cè)趕etup中使用它
setup() {
// 如果沒有用裝飾器,則需要 const form = reactive(new CreateUserForm())
const form = new CreateUserForm()
return {
form
}
}那么這個(gè)類裝飾器是怎么寫的呢,其實(shí)很簡(jiǎn)單
import {reactive} from 'vue-demi'
// vue-demi可以讓你的庫(kù)同時(shí)在vue2(@vue/composition-api)和vue3中使用
//下面這個(gè)函數(shù)Reactive就是類裝飾器,返回的是繼承之后的類。實(shí)例化之后返回的是reactive對(duì)象
function Reactive() {
return function <T extends { new (...args: any[]): {} }>(constructor: T){
return class extends constructor {
constructor(...args: any[]) {
super(...args)
return reactive(this)
}
}
}
}class-validator
然后我們通過(guò)class-validator(github.com/typestack/c… 這個(gè)庫(kù)給我們的表單加上表單驗(yàn)證
import { IsEmail, IsMobilePhone, Length } from 'class-validator'
@Reactive()
export class CreateUserForm {
// 下面這些是屬性裝飾器,用來(lái)標(biāo)記這些屬性的驗(yàn)條件。
// 在驗(yàn)證的時(shí)候會(huì)通過(guò)Reflect拿到這些元數(shù)據(jù)來(lái)驗(yàn)證
// 我們也可以創(chuàng)建自定義的裝飾器
@Length(4, 12)
username: string
@IsEmail()
email: string
@IsMobilePhone('zh-CN')
phone: string
@Length(4, 12)
password: string
}之后在setup中使用,但還是顯得有點(diǎn)粗糙
import { validate } from 'class-validator'
setup() {
const form = new CreateUserForm()
const errors = reactive< { [x in keyof CreateUserForm]: string} >({})
const validate = async () => {
const err = await validate(form)
err.forEach(e => {
if (e.constraints) {
errors[e.property] = Object.values(e.constraints)[0]
}
})
}
return {
form,
errors
}
}
// 用的是jsx,看個(gè)人習(xí)慣
render(){
const {form,errors} = this
return <div>
<div>
<p>
<span>用戶名</span>
<input v-model={form.username}></input>
</p>
{!!errors.username && <p>錯(cuò)誤提示:{errors.username}</p>}
</div>
// ...一些其他表單
<button onClick={() => this.validate()}>驗(yàn)證</button>
</div>
}這里會(huì)有一些需要優(yōu)化的地方
- 在用的時(shí)候每次需要聲明errors和validate方法,不方便
- 需要手動(dòng)點(diǎn)擊驗(yàn)證才會(huì)有表單驗(yàn)證
- 在輸入表單的時(shí)候沒有響應(yīng)式的顯示當(dāng)前字段的錯(cuò)誤提示
封裝Validator
有許多種方法可以優(yōu)化這個(gè),這里我選擇封裝一個(gè)Validator類,有獲取錯(cuò)誤消息和驗(yàn)證的功能,然后讓我們的表單類繼承它。
import { instanceToPlain } from 'class-transformer'
import { validate, ValidationError } from 'class-validator'
import { toRef, watch } from 'vue-demi'
const ERROR = Symbol('error')
const IS_VALID = Symbol('isValid')
// 本來(lái)我打算error的類型簡(jiǎn)單的寫成Record<string,any>
// 但是代碼提示太不友好了,寫成這樣的話,可以完美的提示
// 這個(gè)接口寫起來(lái)很麻煩
type ValidatorError < T > = {
[x in Exclude < keyof T, keyof Validator >] ?:
T[x] extends PropertyKey ? string: ValidatorRequiredError < T[x] >
}
type ValidatorRequiredError < T > = {
[x in Exclude < keyof T, keyof Validator > ] :
T[x] extends PropertyKey ?
string | undefined:
T[x] extends Function ?
T[x] :
ValidatorError < T[x] > |undefined
}
type ValidatorJSON < T > = {
[x in Exclude < keyof T, keyof Validator > ] :
keyof T extends PropertyKey ? T[x] : ValidatorJSON < T[x] >
}
export default abstract class Validator {
// 這里屬性用symbol是為了防止跟表單屬性重復(fù)
private [ERROR]: ValidatorError <this> ={}
private [IS_VALID] : boolean = false
public getError() {
return this[ERROR]
}
public isValid() {
return this[IS_VALID]
}
public toJSON() {
return instanceToPlain(this) as ValidatorJSON < this >
}
public async validate() {
// 一些驗(yàn)證的代碼
}
public clearError() {
this[ERROR] = {}
}
private setError(result: ValidationError[]):Record <string,any > {
// 將error設(shè)置到this[ERROR]上
}
private watchFields(parentKeys ? :string[]) {
// 這里做了單獨(dú)watch每個(gè)屬性,然后單獨(dú)設(shè)置錯(cuò)誤消息
}
}
-----------------------------------------
//上面watchFields這個(gè)方法需要實(shí)例化的時(shí)候單獨(dú)調(diào)用
//所以我們可以放到Reactive裝飾器上,就不需要再手動(dòng)調(diào)用一次了
function Reactive() {
return function <T extends { new (...args: any[]): {} }>(constructor: T){
return class extends constructor {
constructor(...args: any[]) {
super(...args)
const target = reactive(this)
if (target.watchFields) {
target.watchFields()
}
return target
}
}
}
}如果將error的類型簡(jiǎn)單的寫成Record<string,any>,

要是寫成代碼里的那樣子,

具體的代碼可以到(github.com/AndSpark/vu…) 這里看下。
具體使用
好了,現(xiàn)在我們的代碼可以變成這個(gè)樣子。
// ./form.ts
import { Type } from 'class-transformer'
import { IsEmail, IsMobilePhone, IsOptional, Length,
MaxLength, MinLength, ValidateNested } from 'class-validator'
import 'reflect-metadata'
// 需要引入 reflect-metadata 來(lái)使用metadata
class Profile {
@IsOptional()
avatar?: string
@Length(2, 4, {message: '姓名長(zhǎng)度應(yīng)在2到4間'})
realName: string
@IsOptional()
description?: string
}
// 在表單上可以設(shè)置初始值,現(xiàn)在是固定的。
// 那也可以通過(guò)屬性裝飾器調(diào)用api來(lái)設(shè)置動(dòng)態(tài)初始值。大家可以自己實(shí)現(xiàn)試試看
@Reactive()
export class CreateUserForm extends Validator {
@Length(4, 12, { message: '用戶名長(zhǎng)度應(yīng)在4到12間' })
username: string = ''
@IsEmail({}, { message: '請(qǐng)?zhí)顚懻_的郵箱' })
email: string = ''
@IsMobilePhone('zh-CN', null, { message: '請(qǐng)輸入正確的手機(jī)號(hào)碼' })
phone: string = ''
@MinLength(4, { message: '密碼長(zhǎng)度不應(yīng)低于4' })
@MaxLength(12, { message: '密碼長(zhǎng)度不應(yīng)大于12' })
password: string = ''
// 也可以關(guān)聯(lián)其他表單類,但需要下面兩個(gè)裝飾器,用來(lái)關(guān)聯(lián)
@Type(() => Profile)
@ValidateNested()
profile: Profile = new Profile()
}export default defineComponent({
setup() {
const form = new CreateUserForm()
return {
form
}
},
render() {
const { form } = this
return (
<div>
<field label='用戶名' v-model={form.username} error={form.getError().username}></field>
<field label='姓名' v-model={form.profile.realName} error={form.getError().profile?.realName}></field>
<field label='郵箱' v-model={form.email} error={form.getError().email}></field>
<field label='手機(jī)' v-model={form.phone} error={form.getError().phone}></field>
<field label='密碼' v-model={form.password} error={form.getError().password}></field>
<button onClick={() => form.validate()}>驗(yàn)證</button>
<button onClick={() => form.clearError()}>清空錯(cuò)誤</button>
</div>
)
}
})下面是簡(jiǎn)單的頁(yè)面演示。

小結(jié)
其實(shí)裝飾器能做的東西很多,也比較好玩。class-validator這個(gè)庫(kù)里的裝飾器還有很多,大家可以去github上看看。然而它提供的裝飾器可能并不能完全滿足我們的需求,所以還是需要自己去研究裝飾器,去封裝它。像這種表單,如果要設(shè)置初始值,我們也可以使用裝飾器動(dòng)態(tài)的調(diào)用接口來(lái)設(shè)置。
而且我們也許不需要通過(guò)繼承的方式來(lái)實(shí)現(xiàn)驗(yàn)證器,而是通過(guò)將表單類傳入到一個(gè)函數(shù)中,返回驗(yàn)證器。或者把驗(yàn)證器注入到表單中,有很多種方式來(lái)實(shí)現(xiàn)。
表單里的裝飾器大多只用到了屬性裝飾器,其實(shí)方法裝飾器也很有意思,類似攔截器,可以在方法調(diào)用前后執(zhí)行你想要的操作,例如設(shè)置loading狀態(tài),完成錯(cuò)誤處理,防抖節(jié)流等等。
如果大家想要嘗試的話可以在vue中npm install vue-class-validator class-validator class-transformer reflect-metadata,或者去(github.com/AndSpark/vu…) 上看看(點(diǎn)個(gè)star)。
到此這篇關(guān)于VUE中使用TypeScript裝飾器實(shí)現(xiàn)表單驗(yàn)證的文章就介紹到這了,更多相關(guān)VUE實(shí)現(xiàn)表單驗(yàn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue實(shí)現(xiàn)數(shù)據(jù)表格合并列rowspan效果
這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)數(shù)據(jù)表格合并列rowspan效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07
element中el-table局部刷新的實(shí)現(xiàn)示例
本文主要介紹了element中el-table局部刷新的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
VueJs中如何使用Teleport及組件嵌套層次結(jié)構(gòu)詳解
這篇文章主要為大家介紹了VueJs中如何使用Teleport及組件嵌套層次結(jié)構(gòu)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
Vue2.0 slot分發(fā)內(nèi)容與props驗(yàn)證的方法
本篇文章主要介紹了Vue2.0 slot分發(fā)內(nèi)容與props驗(yàn)證的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
vue數(shù)據(jù)push后不能響應(yīng)式更新的問(wèn)題
這篇文章主要介紹了vue數(shù)據(jù)push后不能響應(yīng)式更新的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
vue實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車案例
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
vue省市區(qū)三聯(lián)動(dòng)下拉選擇組件的實(shí)現(xiàn)
本篇文章主要介紹了vue省市區(qū)三聯(lián)動(dòng)下拉選擇組件的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04

