Use TypeScript decorators to implement form validation and other interesting features in Vue

foreword

Hello everyone, I am the last Hibana.

Typescript decorators are a very interesting way of declaring, through decorators you can easily implement the proxy pattern to make code more concise and achieve some other more interesting capabilities. I introduced how to use the decorator and the self-made Validator class to implement form validation in the last article Using TypeScript Decorators in VUE to Implement Form Validation. If you have not seen it, you can go to that article first. class-validatorNext I will use another way to implement form validation.

use useValidator

Before using useValidator, let's take a look at how the form class written earlier looks like.

class Profile {
    @IsOptional()
    avatar?: string

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

    @IsOptional()
    description?: string
}

@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()
}
复制代码

Among them, the @Reactive decorator and the Validator class are encapsulated by myself, and the other form validation decorators use class-validatorlibraries. It seems that there is no problem now, but if there are more forms, each form has to use @Reactive and inherit the Validator class. If there are associated forms, and the associated forms also inherit the Validator class, it will be very bloated.

export default defineComponent({
setup() {
    const form = new CreateUserForm()
    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> 
}})
复制代码

The form from the instance inherits the Validator class and has methods such as getError and validate.

I hope that the form class is a very pure class, only the form and the decorator used for the validation form, so that if there are other needs in the future, it is very convenient to extend, and it will not be pure after inheriting the Validator class.

After that, I implemented a useValidator method to distinguish the form and form validation related. The usage of useValidator is like this.

export default defineComponent({
setup() {
    //往里面传入表单类,然后可以返回这些对象和方法,想用什么就解构出什么。
    const { form, errors, validateForm, clearError, toInit, toJSON } = useValidator(CreateUserForm)
    return () => (
            <div class='container'>
                <field label='用户名' v-model={form.username} error={errors.username}></field> 
                <field label='姓名' v-model={form.profile.avatar} error={errors.profile?.avatar}></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>
                <div>
                    <button onClick={() => validateForm()}>验证</button>
                    <button onClick={() => clearError()}>取消验证</button>
                    <button onClick={() => toInit()}>初始化</button>
                </div>
            </div>
        )
}
})
复制代码

In this way, the value returned by the useValidator method can clearly distinguish each function, and the form is just a pure form. Deconstruct any function you want.

Of course, the functions inside are similar to inherited classes, except that one is to call the method of the class, and the other is to use the method returned by the function. Also a bit of a personal preference.

PS: There is another advantage of using the form decorator, which is a very clear code prompt, as shown below.

tip.gif

There is no need to find out what the form validation of this property looks like. You can see it directly on the property, isn't it very convenient?

Dynamically set initial values ​​for forms

I mentioned in the previous article that the initial value of the form is dynamically set through the interface, so I will simply implement it. For example, I now use @InjectUsername to set the initial value to username, and it will return a 最后的Hibanavalue called username.

class CreateUserForm {
    @InjectUsername()
    @Length(4, 12, { message: '用户名长度应在4到12间'})
    username: string = ''
    //其他....
}
复制代码

Let's simply implement the @InjectUsername decorator.

const api = {
    //一个获取用户名的api
    getUsername() {
        return Promise.resolve('最后的Hibana')
    }
}

const InjectUsername = () => (target: any, key: string) =>
    // 定义一个叫'inject:username'的元数据,传入获取用户名api的方法
    Reflect.defineMetadata('inject', api.getUsername, target, key)

// useInject来实现将api返回的值写入到username中
function useInject<T extends object>(form: T) {
    for (const key in form) {
        const api = Reflect.getMetadata('inject', form, key)
        if (api) {
            api().then(res => {
                form[key] = res
            })
        }
    }
}
复制代码

It is also very simple to use, just call useInject, and the username can be dynamically assigned.

export default defineComponent({
setup() {
    //往里面传入表单类,然后可以返回这些对象和方法,想用什么就解构出什么。
    const { form, errors, validateForm, clearError, toInit, toJSON } = useValidator(CreateUserForm)
    useInject(form)
    return () => ( 
         <div>
             {/* ..... */} 
         </div>
        )
}
})
复制代码

If you don't use decorators, maybe we need to call the interface in setup to assign values. After using the decorator, it can help us hide these operations.

Note: What I have implemented here is the simplest way of writing, in fact, there are many details to pay attention to. For example, most of the api interfaces that are called need to pass parameters, so how should the parameters be passed? The decorator of assignment can also be written to be more general; can useInject return more states, such as loading, error, etc., can it also be? Pass this parameter.

Typescript decorator extension

In fact, decorators can be used not only on form classes, but also on vue components (written in class). Here I also briefly give an example of the possible usage of decorators.

class Page {
    itemList = ref([])
    //可以实现一个Watch装饰器,监听params变化
    @Watch(page => page.loadData())
    params = reactive({
        pageIndex: 1,
        pageSize: 15,
        //...
    })

    loading = ref(false)

    @Message('加载成功','获取数据失败') // 成功/错误 消息提示
    @Loading() // 自动设置loading状态
    async loadData() {
        this.itemList = await getItemList(this.params)
    }
    
    @Debounce(1000) // debounce防止多次点击
    handleLoadClick() {
        this.loadData()
    }
}
复制代码

Then use it in setup, probably like this.

export default defineComponent({
    setup() {
        const {itemList,loading,handleLoadClick} = useInject(Page)
        // ....
    }
})
复制代码

I will not implement the writing method of these decorators here, and interested friends can try it out for themselves.

end

In this article, I introduced the useValidator method to implement form validation, as well as some usage of decorators. As I said at the beginning, Typescript decorators are a very interesting way of declaring, through which decorators can easily implement the proxy pattern to make code more concise and to achieve some other more interesting capabilities. I hope it can arouse your interest in decorators.

In addition, if you are interested in the code related to form validation, you can go to AndSpark/vue-class-validator (github.com) .

Guess you like

Origin juejin.im/post/7077915014182469639