VUE中使用TypeScript装饰器实现表单验证的全过程

 更新时间:2022年03月21日 09:15:19   作者:最后的Hibana  
这篇文章主要给大家介绍了关于如何在VUE中使用TypeScript装饰器实现表单验证的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

前言

最近接触了关于很多TypeScript装饰器的知识,以及class-validator这个用装饰器来做表单验证的包,就萌生了想在vue中使用装饰器来做表单验证的想法。class-validator允许我们在类上通过使用装饰器来完成表单的验证,并且可在浏览器端和node端同时使用。那么接下来先简单介绍一下装饰器和class-validator的用法。

装饰器

装饰器的语法十分简单,只需要在想使用的装饰器前加上@符号,装饰器就会被应用到目标上。 通过装饰器我们可以轻松实现代理模式来使代码更简洁以及实现其它一些更有趣的能力。 关于装饰器的用法我用代码来简单的举例几个,更详细的信息大家可自行去网络上查找。

// 比如我们有一个创建用户的表单,在上面已经应用了一个Reactive类装饰器
// 用了这个装饰器后,这个类实例出来的对象会是响应式对象了。
@Reactive()
export class CreateUserForm {
  username:string
  email:string
  password:string
  confirmPassword:string
}

之后我们在setup中使用它

setup() {
  // 如果没有用装饰器,则需要 const form = reactive(new CreateUserForm())
  const form = new CreateUserForm()
  return {
    form
  }
}

那么这个类装饰器是怎么写的呢,其实很简单

import {reactive} from 'vue-demi'
// vue-demi可以让你的库同时在vue2(@vue/composition-api)和vue3中使用
//下面这个函数Reactive就是类装饰器,返回的是继承之后的类。实例化之后返回的是reactive对象
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

然后我们通过class-validator(github.com/typestack/c… 这个库给我们的表单加上表单验证

import { IsEmail, IsMobilePhone, Length } from 'class-validator'
@Reactive()
export class CreateUserForm {
    // 下面这些是属性装饰器,用来标记这些属性的验条件。
    // 在验证的时候会通过Reflect拿到这些元数据来验证
    // 我们也可以创建自定义的装饰器
    @Length(4, 12)
    username: string
    
    @IsEmail()
    email: string

    @IsMobilePhone('zh-CN')
    phone: string

    @Length(4, 12)
    password: string
}

之后在setup中使用,但还是显得有点粗糙

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,看个人习惯
render(){
  const {form,errors} = this
  return <div>
            <div>
                <p>
                    <span>用户名</span>
                    <input v-model={form.username}></input>
		</p>
                {!!errors.username && <p>错误提示:{errors.username}</p>}
            </div>
            // ...一些其他表单
            <button onClick={() => this.validate()}>验证</button>
        </div>
}

这里会有一些需要优化的地方

  • 在用的时候每次需要声明errors和validate方法,不方便
  • 需要手动点击验证才会有表单验证
  • 在输入表单的时候没有响应式的显示当前字段的错误提示

封装Validator

有许多种方法可以优化这个,这里我选择封装一个Validator类,有获取错误消息和验证的功能,然后让我们的表单类继承它。

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')
// 本来我打算error的类型简单的写成Record<string,any>
// 但是代码提示太不友好了,写成这样的话,可以完美的提示
// 这个接口写起来很麻烦
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是为了防止跟表单属性重复
    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() {
        // 一些验证的代码
    }
    public clearError() {
      this[ERROR] = {}
    }
    private setError(result: ValidationError[]):Record <string,any > {
       // 将error设置到this[ERROR]上
    }
    private watchFields(parentKeys ? :string[]) {
       // 这里做了单独watch每个属性,然后单独设置错误消息
    }
}
    -----------------------------------------    
//上面watchFields这个方法需要实例化的时候单独调用
//所以我们可以放到Reactive装饰器上,就不需要再手动调用一次了
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的类型简单的写成Record<string,any>,

要是写成代码里的那样子,

具体的代码可以到(github.com/AndSpark/vu…) 这里看下。

具体使用

好了,现在我们的代码可以变成这个样子。

// ./form.ts
import { Type } from 'class-transformer'
import { IsEmail, IsMobilePhone, IsOptional, Length,
	MaxLength, MinLength,  ValidateNested } from 'class-validator'
import 'reflect-metadata'
// 需要引入 reflect-metadata 来使用metadata

class Profile {
    @IsOptional()
    avatar?: string

    @Length(2, 4, {message: '姓名长度应在2到4间'})
    realName: string

    @IsOptional()
    description?: string
}

// 在表单上可以设置初始值,现在是固定的。
// 那也可以通过属性装饰器调用api来设置动态初始值。大家可以自己实现试试看 
@Reactive()
export class CreateUserForm extends Validator {
    @Length(4, 12, { message: '用户名长度应在4到12间' })
    username: string = ''

    @IsEmail({}, { message: '请填写正确的邮箱' })
    email: string = ''

    @IsMobilePhone('zh-CN', null, { message: '请输入正确的手机号码' })
    phone: string = ''

    @MinLength(4, { message: '密码长度不应低于4' })
    @MaxLength(12, { message: '密码长度不应大于12' })
    password: string = ''

    // 也可以关联其他表单类,但需要下面两个装饰器,用来关联
    @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='手机' v-model={form.phone} error={form.getError().phone}></field>
            <field label='密码' v-model={form.password} error={form.getError().password}></field>
            <button onClick={() => form.validate()}>验证</button>
            <button onClick={() => form.clearError()}>清空错误</button>
        </div>
    )
}
})

下面是简单的页面演示。

小结

其实装饰器能做的东西很多,也比较好玩。class-validator这个库里的装饰器还有很多,大家可以去github上看看。然而它提供的装饰器可能并不能完全满足我们的需求,所以还是需要自己去研究装饰器,去封装它。像这种表单,如果要设置初始值,我们也可以使用装饰器动态的调用接口来设置。

而且我们也许不需要通过继承的方式来实现验证器,而是通过将表单类传入到一个函数中,返回验证器。或者把验证器注入到表单中,有很多种方式来实现。

表单里的装饰器大多只用到了属性装饰器,其实方法装饰器也很有意思,类似拦截器,可以在方法调用前后执行你想要的操作,例如设置loading状态,完成错误处理,防抖节流等等。

如果大家想要尝试的话可以在vue中npm install vue-class-validator class-validator class-transformer reflect-metadata,或者去(github.com/AndSpark/vu…) 上看看(点个star)。

到此这篇关于VUE中使用TypeScript装饰器实现表单验证的文章就介绍到这了,更多相关VUE实现表单验证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 关于vue中的ajax请求和axios包问题

    关于vue中的ajax请求和axios包问题

    大家在vue中,经常会用到数据请求问题,常用的有vue-resourse、axios ,今天小编给大家介绍下axios的post请求 ,感兴趣的朋友跟随脚本之家小编一起看看吧
    2018-04-04
  • vue实现自定义"模态弹窗"组件实例代码

    vue实现自定义"模态弹窗"组件实例代码

    页面中会有很多时候需要弹窗提示,我们可以写一个弹窗组件,下面这篇文章主要给大家介绍了关于vue实现自定义"模态弹窗"组件的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2021-12-12
  • 用vue写一个日历

    用vue写一个日历

    这篇文章主要介绍了如何利用vue写一个日历,帮助大家更好的理解和学习vue,感兴趣的朋友可以了解下
    2020-11-11
  • vue利用指令实现快速设置元素的高度

    vue利用指令实现快速设置元素的高度

    在项目中经常有需要将列表的高度设置成剩余可视区域的高度,本文主要来和大家介绍一下如何通过指令和css变量的方式快速设置列表高度,希望对大家有所帮助
    2024-03-03
  • VUE前后端学习tab写法实例

    VUE前后端学习tab写法实例

    在本篇文章里小编给大家分享了关于VUE前后端学习tab写法实例以及相关知识点,需要的朋友们参考下。
    2019-08-08
  • 讨论vue中混入mixin的应用

    讨论vue中混入mixin的应用

    这篇文章主要介绍了vue中混入mixin的理解和应用,对vue感兴趣的同学,可以参考下
    2021-05-05
  • Vue全局loading及错误提示的思路与实现

    Vue全局loading及错误提示的思路与实现

    这篇文章主要给大家介绍了关于Vue全局loading及错误提示的思路与实现方法,文中通过示例代码介绍的非常详细,对大家学习或者使用Vue具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-08-08
  • 如何修改element-ui中tree组件的icon图标(小白都会的前端技能)

    如何修改element-ui中tree组件的icon图标(小白都会的前端技能)

    这篇文章主要给大家介绍了关于如何修改element-ui中tree组件的icon图标的相关资料,本文介绍的是小白都会的前端技能,文中通过代码以及图文介绍的非常详细,需要的朋友可以参考下
    2024-01-01
  • vue element-ui动态横向统计表格的实现

    vue element-ui动态横向统计表格的实现

    这篇文章主要介绍了vue element-ui动态横向统计表格的实现方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • 对vue2.0中.vue文件页面跳转之.$router.push的用法详解

    对vue2.0中.vue文件页面跳转之.$router.push的用法详解

    今天小编就为大家分享一篇对vue2.0中.vue文件页面跳转之.$router.push的用法详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08

最新评论