表单验证
表单提交的方式,有的时候除了鼠标点击事件,还可以通过很多其他的方式来提交表单,比如键盘回车等来实现,所以使用form
标签的onSubmit
来提交表单。由于form
表单提交会触发页面刷新,需要阻止默认事件。由于直接formData
的数据,显示的是vue的复杂对象,需要toRaw将其转为简单对象。
设计表单验证的方式一般是制定一个规则,一般是数组或对象的形式来展现,然后错误的数据则以数组的形式来展示。
typescript
// 数组的形式
const rules = [
{ key: 'name', required: true, message: '必填' },
{ key: 'name', pattern: /^.{1,4}$/, message: '只能填 1 到 4 个字符' },
{ key: 'sign', required: true, message: '必填'},
]
// 对象的形式
const rules = {
name: [
{ required: true, message: '必填' },
{ pattern: /^.{1,4}$/, message: '只能填 1 到 4 个字符' }
],
sign: [
{ required: true, message:'必填' }
]
}
const errors = validate(formData, rules)
errors = {
name: ['错误1', '错误2'],
sign: ['错误3', '错误4'],
}
这里采用了数组的形式来设计表单验证方式。type
的定义类型的方式不支持循环使用本身,interface
的定义则可以。validate
函数构造了使用范型的函数。
typescript
// type FData = Record<string,string | number | null | undefined | FData>
// 上面error
interface FData {
[k: string]: string | number | null | undefined | FData
}
type Rule<T> = {
key: keyof T
message: string
} & (
{ type: 'required' } |
{ type: 'pattern', regex: RegExp }
)
type Rules<T> = Rule<T>[]
export type { Rules, Rule, FData }
export const validate = <T extends FData>(formData: T, rules: Rules<T>) => {
type Errors = {
[k in keyof T]?: string[]
}
const errors: Errors = {}
rules.map(rule => {
const { key, type, message } = rule
const value = formData[key]
switch (type) {
case 'required':
if (value === null || value === undefined || value === '') {
errors[key] = errors[key] ?? []
errors[key]?.push(message)
}
break;
case 'pattern':
if (value && !rule.regex.test(value.toString())) {
errors[key] = errors[key] ?? []
errors[key]?.push(message)
}
break;
default:
return
}
})
return errors
}
在vue3中在使用reactive定义复杂结构的响应式数据,如果要对其重新赋值,会丢失其响应性,并不能直接进行赋值,会报错Cannot assign to "form" because it is a constant
。但是可以使用Object.assign()
的方法。
补充:Object.assgin()
方法。用于合并对象,返回新的对象。
tsx
import { defineComponent, PropType, reactive, toRaw } from 'vue'
import { MainLayout } from '../../layouts/MainLayout'
import { Button } from '../../shared/Button'
import { EmojiSelect } from '../../shared/EmojiSelect'
import { Icon } from '../../shared/Icon'
import { Rules, validate } from '../../shared/validate'
import s from './TagCreate.module.scss'
export const TagCreate = defineComponent({
setup: (props, context) => {
const formData = reactive({
name: '',
sign: ''
})
// 可以通过 typeof 获取 formData 的键值
const errors = reactive<{ [k in keyof typeof formData]?: string[] }>({})
const rules: Rules<typeof formData> = [
{ key: 'name', type: 'required', message: '必填' },
{ key: 'name', type: 'pattern', regex: /^.{1,4}$/, message: '只能填 1 到 4 个字符' },
{ key: 'sign', type: 'required', message: '必填' },
]
const onSubmit = (e: Event) => {
// toRaw 返回简单对象
console.log(toRaw(formData))
// 将 errors 进行重置
Object.assign(errors, {
name: undefined,
sign: undefined
})
// errors 进行赋值
Object.assign(errors, validate(formData, rules))
// 阻止默认事件发生
e.preventDefault()
}
return () => (
<MainLayout>{{
title: () => '新建标签',
icon: () => <Icon name='left' onClick={() => { }} />,
default: () => (
<form class={s.form} onSubmit={onSubmit}>
<div class={s.formRow}>
<label class={s.formLabel}>
<span class={s.formItem_name}>标签名</span>
<div class={s.formItem_value}>
<input v-model={formData.name}
class={[s.formItem, s.input, errors['name'] ? s.error : '']}/>
</div>
<div class={s.formItem_errorHint}>
<span>{errors['name'] ? errors['name'][0] : ' '}</span>
</div>
</label>
</div>
<div class={s.formRow}>
<label class={s.formLabel}>
<span class={s.formItem_name}>符号 {formData.sign}</span>
<div class={s.formItem_value}>
<EmojiSelect v-model={formData.sign}
class={[s.formItem, s.emojiList, errors['sign'] ? s.error : '']} />
</div>
<div class={s.formItem_errorHint}>
<span>{errors['sign'] ? errors['sign'][0] : ' '}</span>
</div>
</label>
</div>
<p class={s.tips}>记账时长按标签即可进行编辑</p>
<div class={s.formRow}>
<div class={s.formItem_value}>
<Button class={[s.formItem, s.button]}>确定</Button>
</div>
</div>
</form>
)
}}</MainLayout>
)
}
})
具体详细代码,见链接。