前言
大家好,我是最后的Hibana。
这篇文章主要来给大家分享一种利用Typescript中的装饰器来实现表单组件和校验的方法。我在之前也写了篇关于用装饰器来做表单校验的文章(使用TypeScript装饰器在VUE中实现form校验),没有看过的朋友们也可以去看看。
由于Typescript装饰器十分有意思,所以想分享给大家关于它的一些用法。即使你对装饰器不熟悉也可以继续看下去,它十分简单也很有趣。
接下来我会先用代码来介绍通过装饰器生成表单组件和检验的方法,然后再简单解释下实现的原理。
Form表单类
由于装饰器只能用在类上,所有我们必须使用类的方式来写form表单。那么先来写一个简单的form表单类,然后再给它安上装饰器。
// 这是一个简单的创建user的表单,还没有任何装饰器。
class Profile {
realName: string
description?: string
}
class CreateUserForm {
username: string
email: string
phone: string
password: string
// 如果有表单里还有对象,可以再创建一个类来实现
profile: Profile
}
class-validator装饰器
接下来添加表单校验的装饰器,这里的装饰器用的是一个class-validator库里的装饰器,当然也可以自己实现自定义的检验装饰器。通过装饰器的名称我们可以很轻易的理解它的作用。例如@IsOptional()意思是可选的,@Length()检验了字段长度,@IsEmail()校验邮箱,@IsMobilePhone()校验手机号码等等。
class Profile {
@Length(2, 4, { message: '姓名长度应在2到4间' })
realName: string
@IsOptional()
description?: string
}
class CreateUserForm {
@Length(4, 12, { message: '用户名长度应在4到12间' })
username: string = ''
@ValidateIf(o => !isMobilePhone(o.phone))
@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()
}
那么使用起来也很简单,我们在useValidator方法里传入我们的表单CreateUserForm类,就实现了表单验证功能,它会返回关于表单校验的对象和方法,并且会根据表单变化响应式的判断错误。这里用的是tsx,其中field是自己实现的组件。
export default defineComponent({
setup() {
const { form, errors, validateForm, clearError, toInit, isValid } = useValidator(CreateUserForm)
return () => (
<div class='container'>
<field label='用户名' v-model={form.username} error={errors.username}></field>
<field label='邮箱' v-model={form.email} error={errors.email}></field>
<field label='手机' v-model={form.phone} error={errors.phone}></field>
<field label='密码' v-model={form.password} error={errors.password}></field>
<field label='姓名' v-model={form.profile.realName} error={errors.profile?.realName} ></field>
<div>
<button onClick={() => validateForm()}>验证</button>
<button onClick={() => clearError()}>清除错误</button>
<button onClick={() => toInit()}>初始化</button>
</div>
<p>{isValid.value ? '验证通过' : '验证不通过'}</p>
</div>
)
}
})
很好,我们现在已经实现了form的表单校验,接下来要用装饰器来注册组件。
增加装饰器组件
这里主要用了@Component()和@ComponentNested()两个装饰器,一个用来注册组件,传入自定义的表单组件,然后第二个参数传入props就行了。另一个用来关联表单组件。
class Profile {
@Length(2, 4, { message: '姓名长度应在2到4间' })
@Component(field, { label: '姓名' })
realName: string
@IsOptional()
@Component(field, { label: '描述' })
description?: string
}
class CreateUserForm {
// 自定义的装饰器,用来通过接口来初始化表单属性
@InjectUsername()
@Length(4, 12, { message: '用户名长度应在4到12间' })
@Component(field, { label: '用户名' })
username: string = '还没有名字'
@ValidateIf(o => !isMobilePhone(o.phone))
@IsEmail({}, { message: '请填写正确的邮箱' })
@Component(field, { label: '邮箱' })
email: string = ''
@IsMobilePhone('zh-CN', null, { message: '请输入正确的手机号码' })
@Component(field, { label: '手机' })
phone: string = ''
@MinLength(4, { message: '密码长度不应低于4' })
@MaxLength(12, { message: '密码长度不应大于12' })
@Component(field, { label: '密码' })
password: string = ''
@Type(() => Profile)
@ValidateNested()
@ComponentNested(Profile)
profile: Profile = new Profile()
}
那么接下来就是使用useComponent的时刻了。是的,没错,useComponent会返回与useValidator一样的东西,但是它返回还多了一个component组件,我们可以直接写在下面的tsx中。相比于之前,省去了我们手动在tsx中写组件的功夫。
export default defineComponent({
setup() {
const {component: userForm, form,errors,validateForm, clearError, toInit, isValid } = useComponent(CreateUserForm)
// 通过这个来实现InjectUsername装饰器的功能,异步初始化用户名
useInject(form)
return () => (
<div class='container'>
<user-form />
<div>
<button onClick={() => validateForm()}>验证</button>
<button onClick={() => clearError()}>清除错误</button>
<button onClick={() => toInit()}>初始化</button>
</div>
<p>{isValid.value ? '验证通过' : '验证不通过'}</p>
</div>
)
}
})
那么,来看看效果如何。
简单的useComponent
现在我们已经通过Typescript装饰器在VUE中来生成简单的表单组件和校验功能了,那么是如何实现的呢。关于useValidator的实现,可以去看看我在本文开头分享的文章。
关于component装饰器以及useComponent的实现,其实很简单,代码如下。
export function Component(component: any, props: Record<string, any> = {}) {
return function (target: any, key: string) {
return Reflect.defineMetadata('component', { component, props }, target, key)
}
}
export function ComponentNested<T extends { new (...args: any[]): any }>(constructor: T) {
return function (target: any, key: string) {
return Reflect.defineMetadata('componentNested', constructor, target, key)
}
}
// 通过Component和ComponentNested来收集需要注册的组件
// 在useComponent中获取需要注册的组件,然后返回整个表单组件
export function useComponent<T extends { new (...args: any[]): any }>(constructor: T) {
const { form, errors, validateForm, clearError, toInit, toJSON, isValid } = useValidator(constructor)
const list = getFormComponent(form)
const component = defineComponent({
name: constructor.name,
setup() {
return () => createFormComponent(list, form, errors)
}
})
return { component, form, errors, validateForm, clearError, toInit, toJSON, isValid }
}
// 获取每个属性被注册的表单组件
function getFormComponent(form) {
const componentList: Record<string, any> = {}
for (const key in form) {
if (Object.prototype.hasOwnProperty.call(form, key)) {
const info = Reflect.getMetadata('component', form, key)
const componentNested = Reflect.getMetadata('componentNested', form, key)
if (componentNested) {
componentList[key] = getFormComponent(form[key])
}
if (info) {
componentList[key] = info
}
}
}
return componentList
}
//生成表单组件,当然也可以用jsx来生成
function createFormComponent(componentList, form, errors) {
return h('div', null, [
Object.entries(componentList).map(([key, v]: [string, any]) => {
if (typeof form[key] === 'object') {
return createFormComponent(componentList[key], form[key], errors[key])
}
const props = {
modelValue: form[key],
'onUpdate:modelValue': (value: any) => form[key] = value,
error: errors && errors[key],
...v.props
}
return h(v.component, props)
})
])
}
我这里的实现都是相对比较简单的,当然适用范围也会相对单一。对于一些要求不高,但重复度很高的表单,我觉得用这个也蛮方便的。
结尾
出于对装饰器的喜欢,本篇文章简单介绍了下如何用Typescript装饰器在VUE中来生成表单组件和校验,真的只是入门版本。实现表单组件的生成和校验也有很多种方法,装饰器也只是其中一种罢了。
希望本文能带给大家一些关于装饰器的认识,能想出更多装饰器的可能性。
github地址:github.com/AndSpark/vu…
通过npm来尝试使用看看:
npm install vue-class-validator class-validator class-transformer reflect-metadata
import {useValidator,useComponent,...} from 'vue-class-validator'