【山竹记账前端-vue】12.表单验证
大纲链接 §
[toc]
1. 重构拆分组件,v-model 绑定多个数据 ⇧
- 拆分为两个组件:
EmojiMainTopicNav.tsx、EmojiUnicodeList.tsxEmojiMainTopicNav.tsx- 渲染展示主题列表和当前选中项
- 切换选择主题,更新本地选中项,发布emoji、emojiHint更新事件;
EmojiUnicodeList.tsx- 渲染展示当前主题下的颜文字编码列表和当前选中项
- 入参 selectedMainTopic: 选中的主题
- 监听入参主题变化,清空本地selectedEmojiUnicode,发布emoji、emojiHint更新事件;
- 切换选择颜文字编码,更新本地selectedEmojiUnicode,发布emoji、emojiHint更新事件;
@/modules/tags/emoji/EmojiMainTopicNav.tsx
|
|
@/modules/tags/emoji/EmojiUnicodeList.tsx
|
|
@/modules/tags/EmojiSelector.tsx
|
|
2. 设计表单验证器 ⇧
TagForm.tsx提交前需要验证数据
- 不推荐直接在确定按钮点击事件中写提交逻辑
- 类型为提交
<BarButton type="submit" onClick={() => {}}>确定</BarButton> - 默认点击按钮提交会触发页面刷新,需要使用
e.preventDefault()阻止默认事件
- 类型为提交
- 推荐在
form表单中监听onSubmit事件,可以通过其他方式提交:回车、键盘提交按钮和点击确认等都可触发- 提交时先打印出表单数据
formData为一个reactive代理对象 - 需要转化为原生对象
toRaw(formData),即把代理对象的原始值返回,这个才是需要校验和提交给服务器的数据
- 提交时先打印出表单数据
- 需要将校验逻辑和
form组件解耦- 计算错误
const errors = validate(formData, rules),其中 rules校验规则按顺序一个个独立校验数据,所以是一个数组- 在这个数组中再具体描述需要校验的规则的对象,例如
{ key: 'name', required: true, pattern: /.{1,4}/, message: 'xxx',}, - 但是需要注意的是,为了清晰地表述错误提示信息,必填、验证其他例如字符长度等;需要分开写到不同的校验规则中,并且按照顺序校验
{ key: 'name', required: true, message: '必填', },{ key: 'name', pattern: /^.{1,4}$/, message: '必须1~4字符', },- 每一个校验只能描述一种规则类型
- 将错误信息展示在底部留好的节点中
form_errorHint - 错误的数据结构
const errors = { name: ['错误1', '错误2'], sign: ['错误3', '错误4'],} - 以校验规则中的
key作为属性字段,所有错误信息放在数组中
- 计算错误
@/modules/tags/TagForm.tsx
|
|
3. 实现表单验证器 ⇧
类型的循环引用问题 ⇧
- 数据类型
type FormDataType = Record<string, string | number | null | undefined | FormDataType>- 使用别名类型,递归引用报错
TS2456: Type alias FormDataType circularly references itself.类型别名不可循环引用自身
- 使用别名类型,递归引用报错
- 改用
interface就行了interface FormDataType { [key: string]: string | number | null | undefined | FormDataType }
类型别名的并集互斥属性 ⇧
Rule的类型
|
|
- 注意类型别名并集互斥,需要添加一个额外描述作为重合的属性,来区分
- 即
A | B中需要一个子属性,为互斥的字段,就可以做到互斥 - 添加一个区分属性
type- 可实现
{key: 'name', message: '', type: 'required', required: true, pattern: /123/}, - 提示类型报错
TS2353: Object literal may only specify known properties, and pattern does not exist in type
- 可实现
使用泛型实现 FormDataType 与 RuleType 的关联
FormDataType的[k: string]必须属于Rule的key属性- 注意
tsx中,在箭头函数参数前声明泛型<T>可能报错- 需要额外添加
<T extends Xxx>() => ...
- 需要额外添加
@/shared/validator/formValidate.ts
|
|
- 使用泛型实现了函数两个参数之间的关联
type Rule<T> = { key: keyof T; message: string; } & {...}
T表示FormData的类型,那么Rule的属性key就必须是keyof T
声明 ErrorsType ⇧
- 简单地声明
type Errors = { [k: string]: string[] },但不够精确 - 其中
k也是属于FormDataType的key,必须是其子集type Errors<T extends FormDataType> = { [k in keyof T]?: string[] }
@/shared/validator/formValidate.ts
|
|
- 遍历
rules,解构出各个属性,获取属性值formData[key] - 通过
type字段判断规则类型,最不同分支逻辑 - 通过各个分支验证条件,将规则报错信息放入
errors[key] = [message],条件都是互斥的- 是否必要,是否为空
- 是否满足正则
改成 switch 语法 ⇧
@/shared/validator/formValidate.ts
|
|
- 初始化
errors[key] ||= []等价于errors[key] = errors[key] || [],errors[key]为空值则将其赋值为[] - 可以改为
errors[key] ??= []等价于errors[key] = errors[key] ?? []
参考
两个不足 ⇧
- 不支持自定义校验器
{type: 'custom', validatorFn: ...,} |- 后续实现
- 不支持异步验证:例如判断用户名、邮箱是否全网唯一,需要想后端询问是否有重复
- 前端暂不支持
- 后端可以返回一个类型是
ErrorsType的数据就行了 - 统一错误的数据结构就可以满足所有需求
@/modules/tags/TagForm.tsx
|
|
ts如果声明中间变量,会预先推测变量类型,要么将类型推测得过于宽泛,要么会降类型推测得狭隘了const rules = [{...}, {...}, {...}]宽泛;const rules = [{...}, {...}, {...}]狭隘- 推测后的类型无法判断其中两个属性是互斥的:
regexp与required - 也无法判断其中两个属性必须都存在:
type: 'pattern'与regexp: /hi/ - 所以传参给
formValidate会报类型错误
- 或不声明中间变量,直接将字面量放到参数中,不报类型错误,因为ts就好累推测类型这部,直接判断数据是否满足类型
- 即只做类型对比,不做类型推测
- 推荐的做法是明确地声明其类型
const rules: Rules<FormDataType> = [...]- 也可以类型断言
const rules = [...] as Rules<FormDataType> - 也可以类型满足
const rules = [...] satisfies Rules<FormDataType>既能做类型安全检查,又能保留最具体的字面量类型
- 也可以类型断言
参考
完善错误提示功能,并修正样式 ⇧
从
@/modules/tags/TagForm.tsx获取验证错误结果
|
|
- 获取错误验证结果
errors.value = formValidate(toRaw(formData), rules) - 显示错误提示
<span>{errors.value.name?.join(', ')}</span> - 错误判断样式
errors.value.name ? 'b-[var(--error-color)]' : 'b-[var(--input-border-color)]',
传入子组件
@/modules/tags/EmojiSelector.tsx,完成错误提示逻辑
|
|
- 如果从
ref改为const errors = reactive<{[k in keyof FormDataType]?: string[]}>({}) - 无法重复赋值,只能覆盖key
Object.assign(errors, formValidate(toRaw(formData), rules)) - 修改所有
errors.value的地方 - 添加清空上一次错误的逻辑
Object.assign(errors, {name: undefined, sign: undefined,} ) - 防止验证错误后,错误样式抬高页面结构,可以给一个最小高度的样式
<div class="min-h-18px m-t-.25em text-12px color-[var(--error-color)]"><span>{errors.name?.join(', ')}</span></div><div class="min-h-18px m-t-.25em text-12px color-[var(--error-color)]"><span>{errors.sign?.join(', ')}</span></div>- 也可以使用空格的实体字符占位
<span>$nbsp;</span>或者<span>$emsp;</span> - 需要注意的是 中文字体空格 和 英文字体空格也可能会造成高度样式变动,微软雅黑
Arail空格行高不一样 - 将
<span>$nbsp;</span>替换成中文全角空格即可
4. 使用 pinia 管理tags表单数据,在各个拆分组件中通信 ⇧
·未完待续·
参考文章
相关文章
- 无
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名